Source code for libkatepate.autocomplete

# -*- coding: utf-8 -*-
# Copyright (c) 2013 by Pablo Martín <goinnn@gmail.com>
#
# This software is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this software.  If not, see <http://www.gnu.org/licenses/>.

# This code originally was in this repository:
# <https://github.com/goinnn/Kate-plugins/blob/master/kate_plugins/autopate.py>
# Thanks to Jeroen van Veen <j.veenvan@gmail.com> for the help

import inspect
import os

from PyKDE4.kdecore import i18n
from PyKDE4.kdeui import KIcon
from PyKDE4.ktexteditor import KTextEditor

from PyQt4.QtCore import QModelIndex, QSize, Qt

try:
    import simplejson as json
except ImportError:
    import json


CCM = KTextEditor.CodeCompletionModel


[docs]class AbstractCodeCompletionModel(CCM):
[docs] class GroupPosition: BEST_MATCHES = 1 LOCAL_SCOPE = 100 PUBLIC = 200 PROTECTED = 300 PRIVATE = 400 NAMESPACE = 500 GLOBAL = 600
TITLE_AUTOCOMPLETION = i18n('Autopate') MIMETYPES = [] OPERATORS = [] SEPARATOR = '.' MAX_DESCRIPTION = 80 GROUP_POSITION = GroupPosition.BEST_MATCHES # NOTE Allow to derived classes to override categories set, # as well as its mapping to an icon CATEGORY_2_ICON = { 'package': 'code-block', 'module': 'code-context', 'unknown': None, 'constant': 'code-variable', 'class': 'code-class', 'function': 'code-function', 'pointer': 'unknown' } def __init__(self, model, resultList=None): self.model = model super(AbstractCodeCompletionModel, self).__init__(model) self.resultList = [] @classmethod
[docs] def createItemAutoComplete(cls, text, category='unknown', args=None, description=None): # TODO Add `prefix` parameter if description and 0 < cls.MAX_DESCRIPTION and len(description) > cls.MAX_DESCRIPTION: description = description.strip() description = description[:cls.MAX_DESCRIPTION] + '...' return { 'text': text, 'icon': cls.CATEGORY_2_ICON[category] if category in cls.CATEGORY_2_ICON else None, 'category': category, 'args': args or '', 'type': category, 'description': description or '' }
[docs] def completionInvoked(self, view, word, invocationType): line_start = word.start().line() line_end = word.end().line() column_end = word.end().column() self.resultList = [] self.invocationType = invocationType if line_start != line_end: return None mimetype = view.document().mimeType() if not mimetype in self.MIMETYPES: return None doc = view.document() line = doc.line(line_start) if not line: return line return self.parseLine(line, column_end)
[docs] def data(self, index, role): # Check if 'gorup' node requested if not index.parent().isValid(): # Yep, return title and some other gorup props if role == CCM.InheritanceDepth: return self.GROUP_POSITION # ATTENTION TODO NOTE # Due this BUG (https://bugs.kde.org/show_bug.cgi?id=247896) # we can't use CCM.GroupRole, so hardcoded value 47 is here! if role == 47: return Qt.DisplayRole if role == Qt.DisplayRole: return self.TITLE_AUTOCOMPLETION # Return 'invalid' for other roles return None # Leaf item props are requested item = self.resultList[index.row()] if index.column() == CCM.Name: if role == Qt.DisplayRole: return item['text'] if role == CCM.CompletionRole: return CCM.GlobalScope if role == CCM.ScopeIndex: return -1 # Return 'invalid' for other roles pass elif index.column() == CCM.Icon: if role == Qt.DecorationRole: # Show icon only if specified by a completer! if 'icon' in item and item['icon'] is not None: return KIcon(item['icon']).pixmap(QSize(16, 16)) pass elif index.column() == CCM.Arguments: item_args = item.get('args', None) if role == Qt.DisplayRole and item_args: return item_args elif index.column() == CCM.Postfix: item_description = item.get('description', None) if role == Qt.DisplayRole and item_description: return item_description elif index.column() == CCM.Prefix: # TODO Handle a `prefix` pass return None
[docs] def parent(self, index): if index.internalId(): return self.createIndex(0, 0, 0) else: return QModelIndex()
[docs] def rowCount(self, parent): lenResultList = len(self.resultList) if not parent.isValid() and lenResultList: return 1 elif parent.parent().isValid(): return 0 # Do not make the model look hierarchical else: return lenResultList #http://api.kde.org/4.5-api/kdelibs-apidocs/interfaces/ktexteditor/html/classKTextEditor_1_1CodeCompletionModel.html#3bd60270a94fe2001891651b5332d42b
[docs] def index(self, row, column, parent): if not parent.isValid(): if row == 0: return self.createIndex(row, column, 0) else: return QModelIndex() elif parent.parent().isValid(): return QModelIndex() if row < 0 or row >= len(self.resultList) or column < 0 or column >= CCM.ColumnCount: return QModelIndex() return self.createIndex(row, column, 1)
[docs] def executeCompletionItem(self, document, word, row): # TODO Why this method is not called??? pass
[docs] def getLastExpression(self, line, operators=None): operators = operators or self.OPERATORS opmax = max(operators, key=lambda e: line.rfind(e)) opmax_index = line.rfind(opmax) if line.find(opmax) != -1: line = line[opmax_index + 1:] return line.strip()
[docs] def parseLine(self, line, col_num): return line[:col_num].lstrip()
[docs]class AbstractJSONFileCodeCompletionModel(AbstractCodeCompletionModel): FILE_PATH = None def __init__(self, *args, **kwargs): super(AbstractJSONFileCodeCompletionModel, self).__init__(*args, **kwargs) class_path = inspect.getfile(self.__class__) class_dir = os.sep.join(class_path.split(os.sep)[:-1]) abs_file_path = os.path.join(class_dir, self.FILE_PATH) json_str = open(abs_file_path).read() self.json = json.loads(json_str)
[docs] def getJSON(self, lastExpression, line): return self.json
[docs] def completionInvoked(self, view, word, invocationType): line = super(AbstractJSONFileCodeCompletionModel, self).completionInvoked(view, word, invocationType) if not line: return lastExpression = self.getLastExpression(line) children = self.getChildrenInJSON(lastExpression, self.getJSON(lastExpression, line)) if not children: return for child, attrs in children.items(): index = self.createItemAutoComplete(text=child, category=attrs.get('category', None), args=attrs.get('args', None), description=attrs.get('description', None)) if not index: continue self.resultList.append(index)
[docs] def getChildrenInJSON(self, keys, json): if not self.SEPARATOR in keys: return json keys_split = keys.split(self.SEPARATOR) if keys_split and keys_split[0] in json: keys = self.SEPARATOR.join(keys_split[1:]) return self.getChildrenInJSON(keys, json[keys_split[0]].get('children', None)) return None
[docs]def reset(*args, **kwargs): pass