[syntax_highlight] Simplify plugin code
Fix deprecations warnings in config
This commit is contained in:
@@ -1,123 +0,0 @@
|
||||
# Syntax Highlighting Plugin for Gajim
|
||||
|
||||
[Gajim](https://gajim.org) Plugin that highlights source code blocks in the chat window.
|
||||
|
||||
## Installation
|
||||
|
||||
The recommended way of installing this plugin is to use Gajim's Plugin Installer.
|
||||
|
||||
For more information and instruction on how to install plugins manually, please
|
||||
refer to the [Gajim Plugin Wiki site](https://dev.gajim.org/gajim/gajim-plugins/wikis/home#how-to-install-plugins).
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
This plugin uses markdown-style syntax to identify which parts of a message
|
||||
should be formatted as code in the chat window.
|
||||
|
||||
```
|
||||
Inline source code will be highlighted when placed in between `two single
|
||||
back-ticks`.
|
||||
```
|
||||
|
||||
The language used to highlight the syntax of inline code is selected as the
|
||||
default language in the plugin settings.
|
||||
|
||||
|
||||
Multi-line code blocks are started by three back-ticks followed by a newline.
|
||||
Optionally, a language can be specified directly after the opening back-ticks and
|
||||
before the line break:
|
||||
````
|
||||
```language
|
||||
Note, that the last line of a code block may only contain the closing back-ticks,
|
||||
i.e. there must be a newline here.
|
||||
```
|
||||
````
|
||||
|
||||
In case no language is specified with the opening tag or the specified language
|
||||
could not be identified, the default language configured in the settings is
|
||||
used.
|
||||
|
||||
You can test it by copying and sending the following text to one of your
|
||||
contacts:
|
||||
````
|
||||
```python
|
||||
def test():
|
||||
print("Hello, world!")
|
||||
```
|
||||
````
|
||||
(**Note:** your contact will not receive highlighted text unless she is also
|
||||
using the plugin.)
|
||||
|
||||
|
||||
## Relation to XEP-0393 - 'Message Styling'
|
||||
|
||||
https://xmpp.org/extensions/xep-0393.html#pre-block
|
||||
|
||||
In [XEP-0393](https://xmpp.org/extensions/xep-0393.html),
|
||||
the back-tick based syntax is defined as markup for preformatted
|
||||
text blocks, respectively inline preformatted text.
|
||||
Formatting of such text blocks with mono-spaced fonts is recommended by the XEP.
|
||||
|
||||
By using the same syntax as defined in XEP-0393 XMPP clients with only XEP-0393
|
||||
support but without syntax highlighting can at least present their users blocks
|
||||
of preformatted text.
|
||||
|
||||
Since text in between the back-tick markers is not further formatted by this
|
||||
plugin, it can be considered "preformatted".
|
||||
Hence, this plugin is compatible to the formatting options defined by XEP-0393,
|
||||
[section 5.1.2, "Preformatted Text"](https://xmpp.org/extensions/xep-0393.html#pre-block)
|
||||
and [section 5.2.5, "Preformatted Span"](https://xmpp.org/extensions/xep-0393.html#mono).
|
||||
|
||||
Nevertheless, syntax highlighting for source code is not part of XEP but
|
||||
rather a non-standard extension introduced with this plugin.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
The configuration can be found via 'Gajim' > 'Plugins', then select the
|
||||
'Source Code Syntax Highlight' Plugin and click the gears symbol.
|
||||
The configuration options let you specify many details how code is formatted,
|
||||
including default language, style, font settings, background color and formatting
|
||||
of the code markers.
|
||||
|
||||
In the configuration window, the current settings are displayed in an
|
||||
interactive preview panel. This allows you to directly check how code would
|
||||
look like in the message
|
||||
window.
|
||||
|
||||
## Report Bugs and Feature Requests
|
||||
|
||||
For bug reports, please report them to the [Gajim Plugin Issue tracker](https://dev.gajim.org/gajim/gajim-plugins/issues/new?issue[FlorianMuenchbach]=&issue[description]=Gajim%20Version%3A%20%0APlugin%20Version%3A%0AOperating%20System%3A&issue[title]=[syntax_highlight]).
|
||||
|
||||
Please make sure that the issue you create contains `[syntax_highlight]` in the
|
||||
title and information such as Gajim version, Plugin version, Operating system,
|
||||
etc.
|
||||
|
||||
## Debug
|
||||
|
||||
The plugin adds its own logger. It can be used to set a specific debug level
|
||||
for this plugin and/or filter log messages.
|
||||
|
||||
Run
|
||||
```
|
||||
gajim --loglevel gajim.p.syntax_highlight=DEBUG
|
||||
```
|
||||
in a terminal to display the debug messages.
|
||||
|
||||
|
||||
## Known Issues / ToDo
|
||||
|
||||
* ~~Gajim crashes when correcting a message containing highlighted code.~~
|
||||
(fixed in version 1.1.0)
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
Since I had no experience in writing Plugins for Gajim, I used the
|
||||
[Latex Plugin](https://dev.gajim.org/gajim/gajim-plugins/wikis/LatexPlugin)
|
||||
written by Yves Fischer and Yann Leboulanger as an example and copied a big
|
||||
portion of initial code. Therefore, credits go to the authors of the Latex
|
||||
Plugin for providing an example.
|
||||
|
||||
The syntax highlighting itself is done by [pygments](http://pygments.org/).
|
||||
@@ -8,12 +8,22 @@ from syntax_highlight.gtkformatter import GTKFormatter
|
||||
from syntax_highlight.types import MatchType
|
||||
from syntax_highlight.types import LineBreakOptions
|
||||
from syntax_highlight.types import CodeMarkerOptions
|
||||
from syntax_highlight.types import PLUGIN_INTERNAL_NONE_LEXER_ID
|
||||
|
||||
log = logging.getLogger('gajim.p.syntax_highlight')
|
||||
|
||||
|
||||
class ChatSyntaxHighlighter:
|
||||
def hide_code_markup(self, buf, start, end):
|
||||
def __init__(self, plugin_config, highlighter_config, textview):
|
||||
self.textview = textview
|
||||
self._plugin_config = plugin_config
|
||||
self._highlighter_config = highlighter_config
|
||||
|
||||
def update_config(self, plugin_config):
|
||||
self._plugin_config = plugin_config
|
||||
|
||||
@staticmethod
|
||||
def _hide_code_markup(buf, start, end):
|
||||
tag = buf.get_tag_table().lookup('hide_code_markup')
|
||||
if tag is None:
|
||||
tag = Gtk.TextTag.new('hide_code_markup')
|
||||
@@ -22,17 +32,16 @@ class ChatSyntaxHighlighter:
|
||||
|
||||
buf.apply_tag_by_name('hide_code_markup', start, end)
|
||||
|
||||
def check_line_break(self, is_multiline):
|
||||
line_break = self.config.get_line_break_action()
|
||||
|
||||
def _check_line_break(self, is_multiline):
|
||||
line_break = self._plugin_config['line_break'].value
|
||||
return (line_break == LineBreakOptions.ALWAYS) \
|
||||
or (is_multiline and line_break == LineBreakOptions.MULTILINE)
|
||||
|
||||
def format_code(self, buf, s_tag, s_code, e_tag, e_code, language):
|
||||
style = self.config.get_style_name()
|
||||
if self.config.get_code_marker_setting() == CodeMarkerOptions.HIDE:
|
||||
self.hide_code_markup(buf, s_tag, s_code)
|
||||
self.hide_code_markup(buf, e_code, e_tag)
|
||||
def _format_code(self, buf, s_tag, s_code, e_tag, e_code, language):
|
||||
style = self._plugin_config['style']
|
||||
if self._plugin_config['code_marker'] == CodeMarkerOptions.HIDE:
|
||||
self._hide_code_markup(buf, s_tag, s_code)
|
||||
self._hide_code_markup(buf, e_code, e_tag)
|
||||
else:
|
||||
comment_tag = GTKFormatter.create_tag_for_token(
|
||||
pygments.token.Comment,
|
||||
@@ -49,24 +58,25 @@ class ChatSyntaxHighlighter:
|
||||
lexer = None
|
||||
|
||||
if language is None:
|
||||
lexer = self.config.get_default_lexer()
|
||||
lexer = self._highlighter_config.get_default_lexer()
|
||||
log.info('No Language specified. '
|
||||
'Falling back to default lexer: %s.',
|
||||
self.config.get_default_lexer_name())
|
||||
self._highlighter_config.get_default_lexer_name())
|
||||
else:
|
||||
log.debug('Using lexer for %s.', str(language))
|
||||
lexer = self.config.get_lexer_with_fallback(language)
|
||||
lexer = self._highlighter_config.get_lexer_with_fallback(language)
|
||||
|
||||
if lexer is None:
|
||||
iterator = buf.get_iter_at_mark(start_mark)
|
||||
buf.insert(iterator, '\n')
|
||||
elif not self.config.is_internal_none_lexer(lexer):
|
||||
elif lexer != PLUGIN_INTERNAL_NONE_LEXER_ID:
|
||||
tokens = pygments.lex(code, lexer)
|
||||
|
||||
formatter = GTKFormatter(style=style, start_mark=start_mark)
|
||||
pygments.format(tokens, formatter, buf)
|
||||
|
||||
def find_multiline_matches(self, text):
|
||||
@staticmethod
|
||||
def _find_multiline_matches(text):
|
||||
start = None
|
||||
matches = []
|
||||
# Less strict, allow prefixed whitespaces:
|
||||
@@ -84,7 +94,8 @@ class ChatSyntaxHighlighter:
|
||||
continue
|
||||
return matches
|
||||
|
||||
def find_inline_matches(self, text):
|
||||
@staticmethod
|
||||
def _find_inline_matches(text):
|
||||
"""
|
||||
Inline code is highlighted if the start marker is precedded by a start
|
||||
of line, a whitespace character or either of the other span markers
|
||||
@@ -95,18 +106,19 @@ class ChatSyntaxHighlighter:
|
||||
re.finditer(r'(?:^|\s|\*|~|_)(`((?!`).+?)`)(?:\s|\*|~|_|$)',
|
||||
text)]
|
||||
|
||||
def merge_match_groups(self, real_text, inline_matches, multiline_matches):
|
||||
@staticmethod
|
||||
def _merge_match_groups(real_text, inline_matches, multiline_matches):
|
||||
it_inline = iter(inline_matches)
|
||||
it_multi = iter(multiline_matches)
|
||||
length = len(real_text)
|
||||
|
||||
# Just to get cleaner code below...
|
||||
def get_next(iterator):
|
||||
def _get_next(iterator):
|
||||
return next(iterator, (length, length, ''))
|
||||
|
||||
# In order to simplify the process, we use the 'length' here.
|
||||
cur_inline = get_next(it_inline)
|
||||
cur_multi = get_next(it_multi)
|
||||
cur_inline = _get_next(it_inline)
|
||||
cur_multi = _get_next(it_multi)
|
||||
|
||||
pos = 0
|
||||
|
||||
@@ -142,18 +154,18 @@ class ChatSyntaxHighlighter:
|
||||
# Also, forward the other one, if regions overlap or we took over...
|
||||
if selected[2] == MatchType.INLINE:
|
||||
if cur_multi[0] < cur_inline[1]:
|
||||
cur_multi = get_next(it_multi)
|
||||
cur_inline = get_next(it_inline)
|
||||
cur_multi = _get_next(it_multi)
|
||||
cur_inline = _get_next(it_inline)
|
||||
elif selected[2] == MatchType.MULTILINE:
|
||||
if cur_inline[0] < cur_multi[1]:
|
||||
cur_inline = get_next(it_inline)
|
||||
cur_multi = get_next(it_multi)
|
||||
cur_inline = _get_next(it_inline)
|
||||
cur_multi = _get_next(it_multi)
|
||||
|
||||
return parts
|
||||
|
||||
def process_text(self, real_text, other_tags, _graphics, iter_,
|
||||
_additional):
|
||||
def fix_newline(char, marker_len_no_newline, force=False):
|
||||
def _fix_newline(char, marker_len_no_newline, force=False):
|
||||
fixed = (marker_len_no_newline, '')
|
||||
if char == '\n':
|
||||
fixed = (marker_len_no_newline + 1, '')
|
||||
@@ -164,8 +176,8 @@ class ChatSyntaxHighlighter:
|
||||
buf = self.textview.tv.get_buffer()
|
||||
|
||||
# First, try to find inline or multiline code snippets
|
||||
inline_matches = self.find_inline_matches(real_text)
|
||||
multiline_matches = self.find_multiline_matches(real_text)
|
||||
inline_matches = self._find_inline_matches(real_text)
|
||||
multiline_matches = self._find_multiline_matches(real_text)
|
||||
|
||||
if not inline_matches and not multiline_matches:
|
||||
log.debug('Stopping early, since there is no code block in it...')
|
||||
@@ -177,10 +189,10 @@ class ChatSyntaxHighlighter:
|
||||
start_mark = buf.create_mark('SHP_start', iterator, True)
|
||||
end_mark = buf.create_mark('SHP_end', iterator, False)
|
||||
|
||||
insert_newline_for_multiline = self.check_line_break(True)
|
||||
insert_newline_for_inline = self.check_line_break(False)
|
||||
insert_newline_for_multiline = self._check_line_break(True)
|
||||
insert_newline_for_inline = self._check_line_break(False)
|
||||
|
||||
split_text = self.merge_match_groups(
|
||||
split_text = self._merge_match_groups(
|
||||
real_text, inline_matches, multiline_matches)
|
||||
|
||||
buf.begin_user_action()
|
||||
@@ -204,20 +216,20 @@ class ChatSyntaxHighlighter:
|
||||
language_len = 0 if language is None else len(language)
|
||||
|
||||
# We account the language word width for the front marker
|
||||
front = fix_newline(
|
||||
front = _fix_newline(
|
||||
text_to_insert[0],
|
||||
3 + language_len,
|
||||
insert_newline_for_multiline)
|
||||
back = fix_newline(
|
||||
back = _fix_newline(
|
||||
text_to_insert[-1],
|
||||
3,
|
||||
insert_newline_for_multiline and not end_of_message)
|
||||
else:
|
||||
front = fix_newline(
|
||||
front = _fix_newline(
|
||||
text_to_insert[0],
|
||||
1,
|
||||
insert_newline_for_inline)
|
||||
back = fix_newline(
|
||||
back = _fix_newline(
|
||||
text_to_insert[-1],
|
||||
1,
|
||||
insert_newline_for_inline and not end_of_message)
|
||||
@@ -226,8 +238,9 @@ class ChatSyntaxHighlighter:
|
||||
text_to_insert = ''.join([front[1], text_to_insert, back[1]])
|
||||
|
||||
# Insertion invalidates iterator, let's use our start mark...
|
||||
self.insert_and_format_code(buf, text_to_insert, language,
|
||||
marker_widths, start_mark, end_mark, other_tags)
|
||||
self._insert_and_format_code(
|
||||
buf, text_to_insert, language, marker_widths, start_mark,
|
||||
end_mark, other_tags)
|
||||
|
||||
iterator = buf.get_iter_at_mark(end_mark)
|
||||
# The current end of the buffer's contents is the start for the
|
||||
@@ -244,14 +257,13 @@ class ChatSyntaxHighlighter:
|
||||
# print_special_text method is resetting the plugin_modified variable...
|
||||
self.textview.plugin_modified = True
|
||||
|
||||
def insert_and_format_code(self, buf, insert_text, language, marker,
|
||||
def _insert_and_format_code(self, buf, insert_text, language, marker,
|
||||
start_mark, end_mark, other_tags=None):
|
||||
|
||||
start_iter = buf.get_iter_at_mark(start_mark)
|
||||
|
||||
if other_tags:
|
||||
buf.insert_with_tags_by_name(start_iter, insert_text,
|
||||
*other_tags)
|
||||
buf.insert_with_tags_by_name(start_iter, insert_text, *other_tags)
|
||||
else:
|
||||
buf.insert(start_iter, insert_text)
|
||||
|
||||
@@ -264,20 +276,16 @@ class ChatSyntaxHighlighter:
|
||||
|
||||
log.debug('full text between tags: %s.', tag_start.get_text(tag_end))
|
||||
|
||||
self.format_code(buf, tag_start, s_code, tag_end, e_code, language)
|
||||
self._format_code(buf, tag_start, s_code, tag_end, e_code, language)
|
||||
|
||||
self.textview.plugin_modified = True
|
||||
|
||||
# Set general code block format
|
||||
tag = Gtk.TextTag.new()
|
||||
if self.config.is_bgcolor_override_enabled():
|
||||
tag.set_property('background', self.config.get_bgcolor())
|
||||
tag.set_property('paragraph-background', self.config.get_bgcolor())
|
||||
tag.set_property('font', self.config.get_font())
|
||||
bg_color = self._plugin_config['bgcolor']
|
||||
if self._plugin_config['bgcolor_override']:
|
||||
tag.set_property('background', bg_color)
|
||||
tag.set_property('paragraph-background', bg_color)
|
||||
tag.set_property('font', self._plugin_config['font'])
|
||||
buf.get_tag_table().add(tag)
|
||||
buf.apply_tag(tag, tag_start, tag_end)
|
||||
|
||||
def __init__(self, config, textview):
|
||||
self.last_end_mark = None
|
||||
self.config = config
|
||||
self.textview = textview
|
||||
|
||||
206
syntax_highlight/config_dialog.py
Normal file
206
syntax_highlight/config_dialog.py
Normal file
@@ -0,0 +1,206 @@
|
||||
import logging
|
||||
import re
|
||||
import math
|
||||
from pathlib import Path
|
||||
import pygments
|
||||
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository.Pango import FontDescription
|
||||
from gi.repository.Pango import Style
|
||||
from gi.repository.Pango import SCALE
|
||||
|
||||
from gajim.common import app
|
||||
|
||||
from gajim.plugins.plugins_i18n import _
|
||||
from gajim.plugins.helpers import get_builder
|
||||
|
||||
from syntax_highlight.gtkformatter import GTKFormatter
|
||||
from syntax_highlight.types import LineBreakOptions
|
||||
from syntax_highlight.types import CodeMarkerOptions
|
||||
from syntax_highlight.types import PLUGIN_INTERNAL_NONE_LEXER_ID
|
||||
|
||||
log = logging.getLogger('gajim.p.syntax_highlight')
|
||||
|
||||
PLUGIN_INTERNAL_NONE_LEXER = ('None (monospace only)',
|
||||
PLUGIN_INTERNAL_NONE_LEXER_ID)
|
||||
|
||||
|
||||
class SyntaxHighlighterPluginConfig(Gtk.ApplicationWindow):
|
||||
def __init__(self, plugin, transient):
|
||||
Gtk.ApplicationWindow.__init__(self)
|
||||
self.set_application(app.app)
|
||||
self.set_show_menubar(False)
|
||||
self.set_title(_('Syntax Highlighter Configuration'))
|
||||
self.set_transient_for(transient)
|
||||
self.set_default_size(400, 500)
|
||||
self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
|
||||
self.set_modal(True)
|
||||
self.set_destroy_with_parent(True)
|
||||
|
||||
ui_path = Path(__file__).parent
|
||||
self._ui = get_builder(ui_path.resolve() / 'config_dialog.ui')
|
||||
self.add(self._ui.main_box)
|
||||
self.show_all()
|
||||
|
||||
self._ui.preview_textview.get_buffer().connect(
|
||||
'insert-text', self._on_preview_text_inserted)
|
||||
self._ui.connect_signals(self)
|
||||
|
||||
self._lexer_liststore = Gtk.ListStore(str)
|
||||
self._ui.default_lexer_combobox.set_model(self._lexer_liststore)
|
||||
|
||||
self._style_liststore = Gtk.ListStore(str)
|
||||
self._ui.style_combobox.set_model(self._style_liststore)
|
||||
|
||||
self._plugin = plugin
|
||||
self._lexers = plugin.highlighter_config.get_lexer_list()
|
||||
self._styles = plugin.highlighter_config.get_styles_list()
|
||||
|
||||
self._provider = None
|
||||
self._add_css_provider()
|
||||
|
||||
self._initialize()
|
||||
|
||||
def _initialize(self):
|
||||
default_lexer = self._plugin.highlighter_config.get_default_lexer_name()
|
||||
|
||||
for i, lexer in enumerate(self._lexers):
|
||||
self._lexer_liststore.append([lexer[0]])
|
||||
if lexer[1] == default_lexer:
|
||||
self._ui.default_lexer_combobox.set_active(i)
|
||||
|
||||
for i, style in enumerate(self._styles):
|
||||
self._style_liststore.append([style])
|
||||
if style == self._plugin.config['style']:
|
||||
self._ui.style_combobox.set_active(i)
|
||||
|
||||
self._ui.line_break_combobox.set_active(
|
||||
self._plugin.config['line_break'].value)
|
||||
self._ui.code_marker_combobox.set_active(
|
||||
self._plugin.config['code_marker'])
|
||||
self._ui.font_button.set_font(self._plugin.config['font'])
|
||||
|
||||
bg_override_enabled = self._plugin.config['bgcolor_override']
|
||||
self._ui.bg_color_checkbutton.set_active(bg_override_enabled)
|
||||
self._ui.bg_color_colorbutton.set_sensitive(bg_override_enabled)
|
||||
color = Gdk.RGBA()
|
||||
if color.parse(self._plugin.config['bgcolor']):
|
||||
self._ui.bg_color_colorbutton.set_rgba(color)
|
||||
self._update_preview()
|
||||
|
||||
def _lexer_changed(self, widget):
|
||||
self._plugin.highlighter_config.set_default_lexer(
|
||||
self._lexers[widget.get_active()][1])
|
||||
self._update_preview()
|
||||
|
||||
def _line_break_changed(self, widget):
|
||||
self._plugin.config['line_break'] = LineBreakOptions(
|
||||
widget.get_active())
|
||||
self._update_preview()
|
||||
|
||||
def _code_marker_changed(self, widget):
|
||||
self._plugin.config['code_marker'] = CodeMarkerOptions(
|
||||
widget.get_active())
|
||||
|
||||
def _bg_color_enabled(self, widget):
|
||||
override_color = widget.get_active()
|
||||
self._plugin.config['bgcolor_override'] = override_color
|
||||
self._ui.bg_color_colorbutton.set_sensitive(override_color)
|
||||
self._update_preview()
|
||||
|
||||
def _bg_color_changed(self, widget):
|
||||
color = widget.get_rgba()
|
||||
self._plugin.config['bgcolor'] = color.to_string()
|
||||
self._update_preview()
|
||||
|
||||
def _style_changed(self, widget):
|
||||
style = self._styles[widget.get_active()]
|
||||
if style is not None and style != '':
|
||||
self._plugin.config['style'] = style
|
||||
self._update_preview()
|
||||
|
||||
def _font_changed(self, widget):
|
||||
font = widget.get_font()
|
||||
if font is not None and font != '':
|
||||
self._plugin.config['font'] = font
|
||||
self._update_preview()
|
||||
|
||||
def _update_preview(self):
|
||||
self._format_preview_text()
|
||||
|
||||
def _on_preview_text_inserted(self, _buf, _iterator, text, length, *_args):
|
||||
if (length == 1 and re.match(r'\s', text)) or length > 1:
|
||||
self._format_preview_text()
|
||||
|
||||
def _add_css_provider(self):
|
||||
self._context = self._ui.preview_textview.get_style_context()
|
||||
self._provider = Gtk.CssProvider()
|
||||
self._context.add_provider(
|
||||
self._provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||
self._context.add_class('syntax-preview')
|
||||
|
||||
def _format_preview_text(self):
|
||||
buf = self._ui.preview_textview.get_buffer()
|
||||
start_iter = buf.get_start_iter()
|
||||
start_mark = buf.create_mark(None, start_iter, True)
|
||||
buf.remove_all_tags(start_iter, buf.get_end_iter())
|
||||
|
||||
formatter = GTKFormatter(
|
||||
style=self._plugin.config['style'], start_mark=start_mark)
|
||||
|
||||
code = start_iter.get_text(buf.get_end_iter())
|
||||
lexer = self._plugin.highlighter_config.get_default_lexer()
|
||||
if lexer != PLUGIN_INTERNAL_NONE_LEXER_ID:
|
||||
tokens = pygments.lex(code, lexer)
|
||||
pygments.format(tokens, formatter, buf)
|
||||
|
||||
buf.delete_mark(start_mark)
|
||||
css = self._get_css()
|
||||
self._provider.load_from_data(bytes(css.encode()))
|
||||
|
||||
def _get_css(self):
|
||||
# Build CSS from Pango.FontDescription
|
||||
description = FontDescription.from_string(self._plugin.config['font'])
|
||||
size = description.get_size() / SCALE
|
||||
style = self._get_string_from_pango_style(description.get_style())
|
||||
weight = self._pango_to_css_weight(int(description.get_weight()))
|
||||
family = description.get_family()
|
||||
font = '%spt %s' % (size, family)
|
||||
|
||||
if self._plugin.config['bgcolor_override']:
|
||||
color = self._plugin.config['bgcolor']
|
||||
else:
|
||||
color = '@theme_base_color'
|
||||
|
||||
css = '''
|
||||
.syntax-preview {
|
||||
font: %s;
|
||||
font-weight: %s;
|
||||
font-style: %s;
|
||||
}
|
||||
.syntax-preview > text {
|
||||
background-color: %s;
|
||||
}
|
||||
''' % (font, weight, style, color)
|
||||
return css
|
||||
|
||||
@staticmethod
|
||||
def _pango_to_css_weight(number):
|
||||
# Pango allows for weight values between 100 and 1000
|
||||
# CSS allows only full hundred numbers like 100, 200 ..
|
||||
number = int(number)
|
||||
if number < 100:
|
||||
return 100
|
||||
if number > 900:
|
||||
return 900
|
||||
return int(math.ceil(number / 100.0)) * 100
|
||||
|
||||
@staticmethod
|
||||
def _get_string_from_pango_style(style: Style) -> str:
|
||||
if style == Style.NORMAL:
|
||||
return 'normal'
|
||||
if style == Style.ITALIC:
|
||||
return 'italic'
|
||||
# Style.OBLIQUE:
|
||||
return 'oblique'
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<!-- Generated with glade 3.36.0 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<object class="GtkTextBuffer"/>
|
||||
<object class="GtkListStore" id="code_marker_selection">
|
||||
<columns>
|
||||
@@ -309,7 +309,4 @@
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkTextBuffer" id="textbuffer1">
|
||||
<property name="text">Plug-in decription should be displayed here. This text will be erased during PluginsWindow initialization.</property>
|
||||
</object>
|
||||
</interface>
|
||||
|
||||
@@ -67,7 +67,7 @@ class GTKFormatter(Formatter):
|
||||
|
||||
def format(self, tokensource, outfile):
|
||||
if not isinstance(outfile, Gtk.TextBuffer) or outfile is None:
|
||||
log.warn("Did not get a buffer to format...")
|
||||
log.warning('Did not get a buffer to format...')
|
||||
return
|
||||
buf = outfile
|
||||
|
||||
|
||||
97
syntax_highlight/highlighter_config.py
Normal file
97
syntax_highlight/highlighter_config.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import logging
|
||||
|
||||
from pygments.lexers import get_lexer_by_name
|
||||
from pygments.lexers import get_all_lexers
|
||||
from pygments.styles import get_all_styles
|
||||
from pygments.util import ClassNotFound
|
||||
|
||||
from syntax_highlight.types import PLUGIN_INTERNAL_NONE_LEXER_ID
|
||||
|
||||
log = logging.getLogger('gajim.p.syntax_highlight')
|
||||
PLUGIN_INTERNAL_NONE_LEXER = ('None (monospace only)',
|
||||
PLUGIN_INTERNAL_NONE_LEXER_ID)
|
||||
|
||||
|
||||
class HighlighterConfig:
|
||||
def __init__(self, plugin_config):
|
||||
self._plugin_config = plugin_config
|
||||
|
||||
self._lexer_list = self._create_lexer_list()
|
||||
self._style_list = []
|
||||
for style in get_all_styles():
|
||||
self._style_list.append(style)
|
||||
self._style_list.sort()
|
||||
|
||||
self._default_lexer = None
|
||||
self.set_default_lexer(self._plugin_config['default_lexer'])
|
||||
|
||||
@staticmethod
|
||||
def _create_lexer_list():
|
||||
# The list we create here contains the plain text name and the lexer's
|
||||
# id string
|
||||
lexers = []
|
||||
|
||||
# Iteration over get_all_lexers() seems to be broken somehow
|
||||
# Workaround
|
||||
all_lexers = get_all_lexers()
|
||||
for lexer in all_lexers:
|
||||
# We don't want to add lexers that we cant identify by name later
|
||||
if lexer[1] is not None and lexer[1]:
|
||||
lexers.append((lexer[0], lexer[1][0]))
|
||||
lexers.sort()
|
||||
|
||||
# Insert our internal 'none' type at top of the list
|
||||
lexers.insert(0, PLUGIN_INTERNAL_NONE_LEXER)
|
||||
return lexers
|
||||
|
||||
@staticmethod
|
||||
def get_lexer_by_name(name):
|
||||
lexer = None
|
||||
try:
|
||||
lexer = get_lexer_by_name(name)
|
||||
except ClassNotFound:
|
||||
pass
|
||||
return lexer
|
||||
|
||||
def get_lexer_with_fallback(self, language):
|
||||
lexer = self.get_lexer_by_name(language)
|
||||
if lexer is None:
|
||||
log.info('Falling back to default lexer for %s.',
|
||||
self.get_default_lexer_name())
|
||||
lexer = self._default_lexer[1]
|
||||
return lexer
|
||||
|
||||
def set_default_lexer(self, name):
|
||||
if name != PLUGIN_INTERNAL_NONE_LEXER_ID:
|
||||
lexer = get_lexer_by_name(name)
|
||||
|
||||
if lexer is None and self._default_lexer is None:
|
||||
log.error('Failed to get default lexer by name.'
|
||||
'Falling back to simply using the first lexer '
|
||||
'in the list.')
|
||||
lexer = self._lexer_list[0]
|
||||
name = lexer[0]
|
||||
self._default_lexer = (name, lexer)
|
||||
if lexer is None and self._default_lexer is not None:
|
||||
log.info('Failed to get default lexer by name, keeping '
|
||||
'previous setting (lexer = %s).',
|
||||
self._default_lexer[0])
|
||||
name = self._default_lexer[0]
|
||||
else:
|
||||
self._default_lexer = (name, lexer)
|
||||
else:
|
||||
self._default_lexer = PLUGIN_INTERNAL_NONE_LEXER
|
||||
|
||||
self._plugin_config['default_lexer'] = name
|
||||
|
||||
def get_default_lexer(self):
|
||||
return self._default_lexer[1]
|
||||
|
||||
def get_default_lexer_name(self):
|
||||
return self._default_lexer[0]
|
||||
|
||||
def get_lexer_list(self):
|
||||
return self._lexer_list
|
||||
|
||||
def get_styles_list(self):
|
||||
return self._style_list
|
||||
@@ -1,168 +0,0 @@
|
||||
import logging
|
||||
|
||||
from gi.repository import Gdk
|
||||
|
||||
from pygments.lexers import get_lexer_by_name
|
||||
from pygments.lexers import get_all_lexers
|
||||
from pygments.styles import get_all_styles
|
||||
from pygments.util import ClassNotFound
|
||||
|
||||
from syntax_highlight.types import LineBreakOptions
|
||||
from syntax_highlight.types import CodeMarkerOptions
|
||||
from syntax_highlight.types import PLUGIN_INTERNAL_NONE_LEXER_ID
|
||||
|
||||
log = logging.getLogger('gajim.p.syntax_highlight')
|
||||
|
||||
|
||||
class SyntaxHighlighterConfig:
|
||||
PLUGIN_INTERNAL_NONE_LEXER = ('None (monospace only)',
|
||||
PLUGIN_INTERNAL_NONE_LEXER_ID)
|
||||
|
||||
def _create_lexer_list(self):
|
||||
# The list we create here contains the plain text name and the lexer's
|
||||
# id string
|
||||
lexers = []
|
||||
|
||||
# Iteration over get_all_lexers() seems to be broken somehow
|
||||
# Workaround
|
||||
all_lexers = get_all_lexers()
|
||||
for lexer in all_lexers:
|
||||
# We don't want to add lexers that we cant identify by name later
|
||||
if lexer[1] is not None and lexer[1]:
|
||||
lexers.append((lexer[0], lexer[1][0]))
|
||||
lexers.sort()
|
||||
|
||||
# Insert our internal 'none' type at top of the list
|
||||
lexers.insert(0, self.PLUGIN_INTERNAL_NONE_LEXER)
|
||||
return lexers
|
||||
|
||||
def is_internal_none_lexer(self, lexer):
|
||||
return lexer == PLUGIN_INTERNAL_NONE_LEXER_ID
|
||||
|
||||
def get_internal_none_lexer(self):
|
||||
return self.PLUGIN_INTERNAL_NONE_LEXER
|
||||
|
||||
def get_lexer_by_name(self, name):
|
||||
lexer = None
|
||||
try:
|
||||
lexer = get_lexer_by_name(name)
|
||||
except ClassNotFound:
|
||||
pass
|
||||
return lexer
|
||||
|
||||
def get_lexer_with_fallback(self, language):
|
||||
lexer = self.get_lexer_by_name(language)
|
||||
if lexer is None:
|
||||
log.info('Falling back to default lexer for %s.',
|
||||
self.get_default_lexer_name())
|
||||
lexer = self.default_lexer[1]
|
||||
return lexer
|
||||
|
||||
def set_font(self, font):
|
||||
if font is not None and font != '':
|
||||
self.config['font'] = font
|
||||
|
||||
def set_style(self, style):
|
||||
if style is not None and style != '':
|
||||
self.config['style'] = style
|
||||
|
||||
def set_line_break_action(self, option):
|
||||
if isinstance(option, int):
|
||||
option = LineBreakOptions(option)
|
||||
self.config['line_break'] = option
|
||||
|
||||
def set_default_lexer(self, name):
|
||||
if not self.is_internal_none_lexer(name):
|
||||
lexer = get_lexer_by_name(name)
|
||||
|
||||
if lexer is None and self.default_lexer is None:
|
||||
log.error('Failed to get default lexer by name.'
|
||||
'Falling back to simply using the first lexer '
|
||||
'in the list.')
|
||||
lexer = self.lexer_list[0]
|
||||
name = lexer[0]
|
||||
self.default_lexer = (name, lexer)
|
||||
if lexer is None and self.default_lexer is not None:
|
||||
log.info('Failed to get default lexer by name, keeping '
|
||||
'previous setting (lexer = %s).',
|
||||
self.default_lexer[0])
|
||||
name = self.default_lexer[0]
|
||||
else:
|
||||
self.default_lexer = (name, lexer)
|
||||
else:
|
||||
self.default_lexer = self.PLUGIN_INTERNAL_NONE_LEXER
|
||||
|
||||
self.config['default_lexer'] = name
|
||||
|
||||
def set_bgcolor_override_enabled(self, state):
|
||||
self.config['bgcolor_override'] = state
|
||||
|
||||
def set_bgcolor(self, color):
|
||||
if isinstance(color, Gdk.RGBA):
|
||||
color = color.to_string()
|
||||
self.config['bgcolor'] = color
|
||||
|
||||
def set_code_marker_setting(self, option):
|
||||
if isinstance(option, int):
|
||||
option = CodeMarkerOptions(option)
|
||||
self.config['code_marker'] = option
|
||||
|
||||
def set_pygments_path(self, path):
|
||||
self.config['pygments_path'] = path
|
||||
|
||||
def get_default_lexer(self):
|
||||
return self.default_lexer[1]
|
||||
|
||||
def get_default_lexer_name(self):
|
||||
return self.default_lexer[0]
|
||||
|
||||
def get_lexer_list(self):
|
||||
return self.lexer_list
|
||||
|
||||
def get_line_break_action(self):
|
||||
# Return int only
|
||||
if isinstance(self.config['line_break'], int):
|
||||
# In case of legacy settings, convert.
|
||||
action = self.config['line_break']
|
||||
self.set_line_break_action(action)
|
||||
else:
|
||||
action = self.config['line_break'].value
|
||||
|
||||
return action
|
||||
|
||||
def get_pygments_path(self):
|
||||
return self.config['pygments_path']
|
||||
|
||||
def get_font(self):
|
||||
return self.config['font']
|
||||
|
||||
def get_style_name(self):
|
||||
return self.config['style']
|
||||
|
||||
def is_bgcolor_override_enabled(self):
|
||||
return self.config['bgcolor_override']
|
||||
|
||||
def get_bgcolor(self):
|
||||
return self.config['bgcolor']
|
||||
|
||||
def get_code_marker_setting(self):
|
||||
return self.config['code_marker']
|
||||
|
||||
def get_styles_list(self):
|
||||
return self.style_list
|
||||
|
||||
def init_pygments(self):
|
||||
"""
|
||||
Initialize all config variables that depend directly on pygments being
|
||||
available.
|
||||
"""
|
||||
self.lexer_list = self._create_lexer_list()
|
||||
self.style_list = [s for s in get_all_styles()]
|
||||
self.style_list.sort()
|
||||
self.set_default_lexer(self.config['default_lexer'])
|
||||
|
||||
def __init__(self, config):
|
||||
self.lexer_list = []
|
||||
self.style_list = []
|
||||
self.config = config
|
||||
self.default_lexer = None
|
||||
@@ -1,151 +0,0 @@
|
||||
import re
|
||||
import pygments
|
||||
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository.Pango import FontDescription
|
||||
|
||||
from gajim.plugins.gui import GajimPluginConfigDialog
|
||||
from gajim.plugins.helpers import get_builder
|
||||
|
||||
from syntax_highlight.gtkformatter import GTKFormatter
|
||||
from syntax_highlight.types import LineBreakOptions
|
||||
from syntax_highlight.types import CodeMarkerOptions
|
||||
|
||||
|
||||
class SyntaxHighlighterPluginConfiguration(GajimPluginConfigDialog):
|
||||
def init(self):
|
||||
path = self.plugin.local_file_path('config_dialog.ui')
|
||||
self._ui = get_builder(path)
|
||||
box = self.get_content_area()
|
||||
box.pack_start(self._ui.main_box, True, True, 0)
|
||||
|
||||
self._ui.set_translation_domain('gajim_plugins')
|
||||
|
||||
self.liststore = Gtk.ListStore(str)
|
||||
self._ui.default_lexer_combobox.set_model(self.liststore)
|
||||
|
||||
self.style_liststore = Gtk.ListStore(str)
|
||||
self._ui.style_combobox.set_model(self.style_liststore)
|
||||
|
||||
self._ui.preview_textview.get_buffer().connect(
|
||||
'insert-text', self._on_preview_text_inserted)
|
||||
|
||||
self._ui.connect_signals(self)
|
||||
|
||||
self.default_lexer_id = 0
|
||||
self.style_id = 0
|
||||
|
||||
def set_config(self, config):
|
||||
self.config = config
|
||||
self.lexers = self.config.get_lexer_list()
|
||||
self.styles = self.config.get_styles_list()
|
||||
default_lexer = self.config.get_default_lexer_name()
|
||||
default_style = self.config.get_style_name()
|
||||
|
||||
for i, lexer in enumerate(self.lexers):
|
||||
self.liststore.append([lexer[0]])
|
||||
if lexer[1] == default_lexer:
|
||||
self.default_lexer_id = i
|
||||
|
||||
for i, style in enumerate(self.styles):
|
||||
self.style_liststore.append([style])
|
||||
if style == default_style:
|
||||
self.style_id = i
|
||||
self._update_preview()
|
||||
|
||||
def _lexer_changed(self, _widget):
|
||||
new = self._ui.default_lexer_combobox.get_active()
|
||||
if new != self.default_lexer_id:
|
||||
self.default_lexer_id = new
|
||||
self.config.set_default_lexer(self.lexers[self.default_lexer_id][1])
|
||||
self._update_preview()
|
||||
|
||||
def _line_break_changed(self, _widget):
|
||||
new = LineBreakOptions(self._ui.line_break_combobox.get_active())
|
||||
if new != self.config.get_line_break_action():
|
||||
self.config.set_line_break_action(new)
|
||||
self._update_preview()
|
||||
|
||||
def _code_marker_changed(self, _widget):
|
||||
new = CodeMarkerOptions(self._ui.code_marker_combobox.get_active())
|
||||
if new != self.config.get_code_marker_setting():
|
||||
self.config.set_code_marker_setting(new)
|
||||
|
||||
def _bg_color_enabled(self, _widget):
|
||||
new = self._ui.bg_color_checkbutton.get_active()
|
||||
if new != self.config.is_bgcolor_override_enabled():
|
||||
bg_override_enabled = new
|
||||
self.config.set_bgcolor_override_enabled(bg_override_enabled)
|
||||
self._ui.bg_color_colorbutton.set_sensitive(bg_override_enabled)
|
||||
self._update_preview()
|
||||
|
||||
def _bg_color_changed(self, _widget):
|
||||
new = self._ui.bg_color_colorbutton.get_rgba()
|
||||
if new != self.config.get_bgcolor():
|
||||
self.config.set_bgcolor(new)
|
||||
self._update_preview()
|
||||
|
||||
def _style_changed(self, _widget):
|
||||
new = self._ui.style_combobox.get_active()
|
||||
if new != self.style_id:
|
||||
self.style_id = new
|
||||
self.config.set_style(self.styles[self.style_id])
|
||||
self._update_preview()
|
||||
|
||||
def _font_changed(self, _widget):
|
||||
new = self._ui.font_button.get_font()
|
||||
if new != self.config.get_font():
|
||||
self.config.set_font(new)
|
||||
self._update_preview()
|
||||
|
||||
def _update_preview(self):
|
||||
self._format_preview_text()
|
||||
|
||||
def _on_preview_text_inserted(self, _buf, _iterator, text, length, *_args):
|
||||
if (length == 1 and re.match(r'\s', text)) or length > 1:
|
||||
self._format_preview_text()
|
||||
|
||||
def _format_preview_text(self):
|
||||
buf = self._ui.preview_textview.get_buffer()
|
||||
start_iter = buf.get_start_iter()
|
||||
start_mark = buf.create_mark(None, start_iter, True)
|
||||
buf.remove_all_tags(start_iter, buf.get_end_iter())
|
||||
|
||||
formatter = GTKFormatter(
|
||||
style=self.config.get_style_name(), start_mark=start_mark)
|
||||
|
||||
code = start_iter.get_text(buf.get_end_iter())
|
||||
lexer = self.config.get_default_lexer()
|
||||
if not self.config.is_internal_none_lexer(lexer):
|
||||
tokens = pygments.lex(code, lexer)
|
||||
pygments.format(tokens, formatter, buf)
|
||||
|
||||
buf.delete_mark(start_mark)
|
||||
|
||||
self._ui.preview_textview.override_font(
|
||||
FontDescription.from_string(self.config.get_font()))
|
||||
|
||||
color = Gdk.RGBA()
|
||||
if color.parse(self.config.get_bgcolor()):
|
||||
self._ui.preview_textview.override_background_color(
|
||||
Gtk.StateFlags.NORMAL, color)
|
||||
|
||||
def on_run(self):
|
||||
self._ui.default_lexer_combobox.set_active(self.default_lexer_id)
|
||||
self._ui.line_break_combobox.set_active(
|
||||
self.config.get_line_break_action())
|
||||
self._ui.code_marker_combobox.set_active(
|
||||
self.config.get_code_marker_setting())
|
||||
self._ui.style_combobox.set_active(self.style_id)
|
||||
|
||||
self._ui.font_button.set_font(self.config.get_font())
|
||||
|
||||
bg_override_enabled = self.config.is_bgcolor_override_enabled()
|
||||
self._ui.bg_color_checkbutton.set_active(bg_override_enabled)
|
||||
|
||||
self._ui.bg_color_colorbutton.set_sensitive(bg_override_enabled)
|
||||
|
||||
color = Gdk.RGBA()
|
||||
if color.parse(self.config.get_bgcolor()):
|
||||
self._ui.bg_color_colorbutton.set_rgba(color)
|
||||
@@ -1,111 +1,91 @@
|
||||
import logging
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
from gajim.plugins import GajimPlugin
|
||||
from gajim.plugins.plugins_i18n import _
|
||||
|
||||
from syntax_highlight.types import LineBreakOptions
|
||||
from syntax_highlight.types import CodeMarkerOptions
|
||||
from syntax_highlight.types import PLUGIN_INTERNAL_NONE_LEXER_ID
|
||||
if sys.version_info >= (3, 4):
|
||||
from importlib.util import find_spec as find_module
|
||||
else:
|
||||
from importlib import find_loader as find_module
|
||||
|
||||
PYGMENTS_MISSING = 'You are missing Python-Pygments.'
|
||||
|
||||
log = logging.getLogger('gajim.p.syntax_highlight')
|
||||
|
||||
|
||||
def try_loading_pygments():
|
||||
success = find_module('pygments') is not None
|
||||
if success:
|
||||
HAS_PYGMENTS = False
|
||||
try:
|
||||
from syntax_highlight.chat_syntax_highlighter import \
|
||||
ChatSyntaxHighlighter
|
||||
from syntax_highlight.plugin_config_dialog import \
|
||||
SyntaxHighlighterPluginConfiguration
|
||||
from syntax_highlight.plugin_config import SyntaxHighlighterConfig
|
||||
global SyntaxHighlighterPluginConfiguration
|
||||
global ChatSyntaxHighlighter
|
||||
global SyntaxHighlighterConfig
|
||||
success = True
|
||||
log.debug("pygments loaded.")
|
||||
from syntax_highlight.chat_syntax_highlighter import ChatSyntaxHighlighter
|
||||
from syntax_highlight.config_dialog import SyntaxHighlighterPluginConfig
|
||||
from syntax_highlight.highlighter_config import HighlighterConfig
|
||||
HAS_PYGMENTS = True
|
||||
except Exception as exception:
|
||||
log.error("Import Error: %s.", exception)
|
||||
success = False
|
||||
|
||||
return success
|
||||
log.error('Could not load pygments: %s', exception)
|
||||
|
||||
|
||||
class SyntaxHighlighterPlugin(GajimPlugin):
|
||||
def on_connect_with_chat_control(self, chat_control):
|
||||
account = chat_control.contact.account.name
|
||||
jid = chat_control.contact.jid
|
||||
if account not in self.ccontrol:
|
||||
self.ccontrol[account] = {}
|
||||
self.ccontrol[account][jid] = ChatSyntaxHighlighter(
|
||||
self.conf, chat_control.conv_textview)
|
||||
|
||||
def on_disconnect_from_chat_control(self, chat_control):
|
||||
account = chat_control.contact.account.name
|
||||
jid = chat_control.contact.jid
|
||||
del self.ccontrol[account][jid]
|
||||
|
||||
def on_print_real_text(self, text_view, real_text, other_tags, graphics,
|
||||
iterator, additional):
|
||||
account = text_view.account
|
||||
for jid in self.ccontrol[account]:
|
||||
if self.ccontrol[account][jid].textview != text_view:
|
||||
continue
|
||||
self.ccontrol[account][jid].process_text(
|
||||
real_text, other_tags, graphics, iterator, additional)
|
||||
return
|
||||
|
||||
def try_init(self):
|
||||
"""
|
||||
Separating this part of the initialization from the init() method
|
||||
allows repeating this step again, without reloading the plugin,
|
||||
i.e. restarting Gajim for instance.
|
||||
Doing so allows resolving the dependency issues without restart :)
|
||||
"""
|
||||
pygments_loaded = try_loading_pygments()
|
||||
if not pygments_loaded:
|
||||
return False
|
||||
|
||||
self.activatable = True
|
||||
self.available_text = None
|
||||
self.config_dialog = SyntaxHighlighterPluginConfiguration(self)
|
||||
|
||||
self.conf = SyntaxHighlighterConfig(self.config)
|
||||
# The following initialization requires pygments to be available.
|
||||
self.conf.init_pygments()
|
||||
|
||||
self.config_dialog = SyntaxHighlighterPluginConfiguration(self)
|
||||
self.config_dialog.set_config(self.conf)
|
||||
|
||||
self.gui_extension_points = {
|
||||
'chat_control_base': (
|
||||
self.on_connect_with_chat_control,
|
||||
self.on_disconnect_from_chat_control),
|
||||
'print_real_text': (self.on_print_real_text, None), }
|
||||
return True
|
||||
|
||||
def init(self):
|
||||
self.ccontrol = {}
|
||||
self.description = _(
|
||||
'Source code syntax highlighting in the chat window.\n\n'
|
||||
'Markdown-style syntax is supported, i.e. text inbetween '
|
||||
'`single backticks` is rendered as inline code.\n'
|
||||
'```language\n'
|
||||
'selection is possible in multi-line code snippets inbetween '
|
||||
'triple-backticks\n'
|
||||
'Note the newlines in this case…\n'
|
||||
'```\n\n'
|
||||
'Changed settings will take effect after re-opening the message '
|
||||
'tab/window.')
|
||||
|
||||
self.config_default_values = {
|
||||
'default_lexer': (PLUGIN_INTERNAL_NONE_LEXER_ID, ''),
|
||||
'line_break': (LineBreakOptions.MULTILINE, ''),
|
||||
'style': ('default', ''),
|
||||
'font': ('Monospace 10', ''),
|
||||
'bgcolor': ('#ccc', ''),
|
||||
'bgcolor': ('rgb(200, 200, 200)', ''),
|
||||
'bgcolor_override': (True, ''),
|
||||
'code_marker': (CodeMarkerOptions.AS_COMMENT, ''),
|
||||
'pygments_path': (None, ''), }
|
||||
}
|
||||
|
||||
is_initialized = self.try_init()
|
||||
self.gui_extension_points = {
|
||||
'chat_control_base': (
|
||||
self._connect_chat_control,
|
||||
self._disconnect_chat_control),
|
||||
'print_real_text': (self._on_print_real_text, None)
|
||||
}
|
||||
|
||||
if not is_initialized:
|
||||
if not HAS_PYGMENTS:
|
||||
self.activatable = False
|
||||
self.available_text = PYGMENTS_MISSING
|
||||
self.available_text = _('You are missing python-pygments.')
|
||||
self.config_dialog = None
|
||||
|
||||
self._migrate_settings()
|
||||
self._highlighters = {}
|
||||
self.config_dialog = partial(SyntaxHighlighterPluginConfig, self)
|
||||
self.highlighter_config = HighlighterConfig(self.config)
|
||||
|
||||
def _migrate_settings(self):
|
||||
line_break = self.config['line_break']
|
||||
if isinstance(line_break, int):
|
||||
self.config['line_break'] = LineBreakOptions(line_break)
|
||||
|
||||
def _connect_chat_control(self, chat_control):
|
||||
highlighter = ChatSyntaxHighlighter(
|
||||
self.config, self.highlighter_config, chat_control.conv_textview)
|
||||
self._highlighters[chat_control.control_id] = highlighter
|
||||
|
||||
def _disconnect_chat_control(self, chat_control):
|
||||
highlighter = self._highlighters.get(chat_control.control_id)
|
||||
if highlighter is not None:
|
||||
del highlighter
|
||||
self._highlighters.pop(chat_control.control_id, None)
|
||||
|
||||
def _on_print_real_text(self, text_view, real_text, other_tags, graphics,
|
||||
iterator, additional):
|
||||
for highlighter in self._highlighters.values():
|
||||
if highlighter.textview != text_view:
|
||||
continue
|
||||
highlighter.process_text(
|
||||
real_text, other_tags, graphics, iterator, additional)
|
||||
return
|
||||
|
||||
def update_highlighters(self):
|
||||
for highlighter in self._highlighters.values():
|
||||
highlighter.update_config(self.config)
|
||||
|
||||
Reference in New Issue
Block a user