Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim-plugins.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Brötzmann <mailtrash@posteo.de>2020-04-16 19:13:30 +0300
committerlovetox <philipp@hoerist.com>2020-06-29 22:54:50 +0300
commit83f85dd3e6cf06db2ee4e3a3c1f30949e27f8ffb (patch)
tree0743c155684661489f5dcc3473cc20579224f42c
parentae8ce09a1551e767e931d2a53334f22e91a65bed (diff)
[syntax_highlight] Simplify plugin code
Fix deprecations warnings in config
-rw-r--r--syntax_highlight/README.md123
-rw-r--r--syntax_highlight/chat_syntax_highlighter.py108
-rw-r--r--syntax_highlight/config_dialog.py206
-rw-r--r--syntax_highlight/config_dialog.ui7
-rw-r--r--syntax_highlight/gtkformatter.py2
-rw-r--r--syntax_highlight/highlighter_config.py97
-rw-r--r--syntax_highlight/plugin_config.py168
-rw-r--r--syntax_highlight/plugin_config_dialog.py151
-rw-r--r--syntax_highlight/syntax_highlight.py150
9 files changed, 429 insertions, 583 deletions
diff --git a/syntax_highlight/README.md b/syntax_highlight/README.md
deleted file mode 100644
index 29f16cf..0000000
--- a/syntax_highlight/README.md
+++ /dev/null
@@ -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/).
diff --git a/syntax_highlight/chat_syntax_highlighter.py b/syntax_highlight/chat_syntax_highlighter.py
index c18cc18..338dd62 100644
--- a/syntax_highlight/chat_syntax_highlighter.py
+++ b/syntax_highlight/chat_syntax_highlighter.py
@@ -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):
+ _additional):
+ 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,
- start_mark, end_mark, other_tags=None):
+ 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
diff --git a/syntax_highlight/config_dialog.py b/syntax_highlight/config_dialog.py
new file mode 100644
index 0000000..0cfc95e
--- /dev/null
+++ b/syntax_highlight/config_dialog.py
@@ -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'
diff --git a/syntax_highlight/config_dialog.ui b/syntax_highlight/config_dialog.ui
index 64328d3..09a2764 100644
--- a/syntax_highlight/config_dialog.ui
+++ b/syntax_highlight/config_dialog.ui
@@ -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>
diff --git a/syntax_highlight/gtkformatter.py b/syntax_highlight/gtkformatter.py
index d7347f0..823f1ad 100644
--- a/syntax_highlight/gtkformatter.py
+++ b/syntax_highlight/gtkformatter.py
@@ -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
diff --git a/syntax_highlight/highlighter_config.py b/syntax_highlight/highlighter_config.py
new file mode 100644
index 0000000..061ccee
--- /dev/null
+++ b/syntax_highlight/highlighter_config.py
@@ -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
diff --git a/syntax_highlight/plugin_config.py b/syntax_highlight/plugin_config.py
deleted file mode 100644
index ebed683..0000000
--- a/syntax_highlight/plugin_config.py
+++ /dev/null
@@ -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
diff --git a/syntax_highlight/plugin_config_dialog.py b/syntax_highlight/plugin_config_dialog.py
deleted file mode 100644
index c33e2b1..0000000
--- a/syntax_highlight/plugin_config_dialog.py
+++ /dev/null
@@ -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)
diff --git a/syntax_highlight/syntax_highlight.py b/syntax_highlight/syntax_highlight.py
index c041d02..00190c8 100644
--- a/syntax_highlight/syntax_highlight.py
+++ b/syntax_highlight/syntax_highlight.py
@@ -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:
- 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.")
- except Exception as exception:
- log.error("Import Error: %s.", exception)
- success = False
-
- return success
+HAS_PYGMENTS = False
+try:
+ 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('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)