~ubuntu-branches/ubuntu/vivid/frescobaldi/vivid

« back to all changes in this revision

Viewing changes to python/frescobaldi_app/completion.py

  • Committer: Package Import Robot
  • Author(s): Ryan Kavanagh
  • Date: 2012-01-03 16:20:11 UTC
  • mfrom: (1.4.1)
  • Revision ID: package-import@ubuntu.com-20120103162011-tsjkwl4sntwmprea
Tags: 2.0.0-1
* New upstream release 
* Drop the following uneeded patches:
  + 01_checkmodules_no_python-kde4_build-dep.diff
  + 02_no_pyc.diff
  + 04_no_binary_lilypond_upgrades.diff
* Needs new dependency python-poppler-qt4
* Update debian/watch for new download path
* Update copyright file with new holders and years

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
2
 
#
3
 
# Copyright (c) 2008, 2009, 2010 by Wilbert Berendsen
4
 
#
5
 
# This program is free software; you can redistribute it and/or
6
 
# modify it under the terms of the GNU General Public License
7
 
# as published by the Free Software Foundation; either version 2
8
 
# of the License, or (at your option) any later version.
9
 
#
10
 
# This program is distributed in the hope that it will be useful,
11
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 
# GNU General Public License for more details.
14
 
#
15
 
# You should have received a copy of the GNU General Public License
16
 
# along with this program; if not, write to the Free Software
17
 
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
 
# See http://www.gnu.org/licenses/ for more information.
19
 
 
20
 
from __future__ import unicode_literals
21
 
 
22
 
"""
23
 
LilyPond auto completion
24
 
"""
25
 
 
26
 
import re
27
 
 
28
 
from PyQt4.QtCore import QModelIndex, Qt
29
 
from PyQt4.QtGui import QBrush, QColor, QTextFormat
30
 
from PyKDE4.kdecore import KGlobal
31
 
from PyKDE4.ktexteditor import KTextEditor
32
 
 
33
 
import ly.tokenize, ly.version, ly.words, ly.colors
34
 
import frescobaldi_app.version
35
 
from frescobaldi_app.mainapp import lilyPondCommand
36
 
 
37
 
 
38
 
class CompletionHelper(object):
39
 
    """
40
 
    Helper class that contains a list of completions.
41
 
    """
42
 
    # roles on the Name column
43
 
    roles = {
44
 
        KTextEditor.CodeCompletionModel.CompletionRole:
45
 
            KTextEditor.CodeCompletionModel.FirstProperty |
46
 
            KTextEditor.CodeCompletionModel.Public |
47
 
            KTextEditor.CodeCompletionModel.LastProperty,
48
 
        KTextEditor.CodeCompletionModel.ScopeIndex: 0,
49
 
        KTextEditor.CodeCompletionModel.MatchQuality: 10,
50
 
        KTextEditor.CodeCompletionModel.HighlightingMethod: None,
51
 
        KTextEditor.CodeCompletionModel.InheritanceDepth: 0,
52
 
    }
53
 
    
54
 
    def __init__(self, model, resultList=None):
55
 
        """
56
 
        model is the KTextEditor.CodeCompletionModel helped
57
 
        by this object.
58
 
        """
59
 
        self.model = model
60
 
        self.resultList = resultList or []
61
 
    
62
 
    def index(self, row, column, parent):
63
 
        if (row < 0 or row >= len(self.resultList) or
64
 
            column < 0 or column >= KTextEditor.CodeCompletionModel.ColumnCount or
65
 
            parent.isValid()):
66
 
            return QModelIndex()
67
 
        return self.model.createIndex(row, column, 0)
68
 
        
69
 
    def rowCount(self, parent):
70
 
        if parent.isValid():
71
 
            return 0 # Do not make the model look hierarchical
72
 
        else:
73
 
            return len(self.resultList)
74
 
 
75
 
    def data(self, index, role):
76
 
        if index.column() == KTextEditor.CodeCompletionModel.Name:
77
 
            if role == Qt.DisplayRole:
78
 
                return self.resultList[index.row()]
79
 
            try:
80
 
                return self.roles[role]
81
 
            except KeyError:
82
 
                pass
83
 
    
84
 
    def executeCompletionItem(self, doc, word, row):
85
 
        pass
86
 
 
87
 
 
88
 
class CompletionList(CompletionHelper):
89
 
    """
90
 
    Contains completions presented as a simple list.
91
 
    """
92
 
    def executeCompletionItem(self, doc, word, row):
93
 
        text = self.resultList[row]
94
 
        if '{}' in text:
95
 
            text = text.replace('{}', '{\n(|)\n}')
96
 
            self.model.doc.manipulator().insertTemplate(text, word.start(), word)
97
 
            return True
98
 
 
99
 
 
100
 
class VarCompletions(CompletionHelper):
101
 
    """
102
 
    List of vars, that get ' = ' after themselves.
103
 
    """
104
 
    def executeCompletionItem(self, doc, word, row):
105
 
        text = self.resultList[row]
106
 
        line = doc.line(word.end().line())[word.end().column():]
107
 
        if not line.lstrip().startswith('='):
108
 
            text += ' = '
109
 
        doc.replaceText(word, text)
110
 
        return True
111
 
 
112
 
 
113
 
class ColorCompletions(CompletionHelper):
114
 
    """
115
 
    Completions with color, that show the color name highlighted
116
 
    """
117
 
    roles = CompletionHelper.roles.copy()
118
 
    roles.update({
119
 
        KTextEditor.CodeCompletionModel.HighlightingMethod:
120
 
            KTextEditor.CodeCompletionModel.CustomHighlighting
121
 
    })
122
 
    
123
 
    def data(self, index, role):
124
 
        if index.column() == KTextEditor.CodeCompletionModel.Name:
125
 
            name, (r, g, b) = self.resultList[index.row()]
126
 
            if role == Qt.DisplayRole:
127
 
                return name
128
 
            elif role == KTextEditor.CodeCompletionModel.CustomHighlight:
129
 
                format = QTextFormat()
130
 
                color = QColor.fromRgbF(r, g, b)
131
 
                format.setBackground(QBrush(color))
132
 
                return [0, len(name), format]
133
 
        return super(ColorCompletions, self).data(index, role)
134
 
 
135
 
 
136
 
class ExpansionCompletions(CompletionHelper):
137
 
    """
138
 
    Looks in the expansions, but skips expansions.
139
 
    """
140
 
    def __init__(self, model):
141
 
        self.mgr = model.doc.app.mainwin.expandManager()
142
 
        self.expansions = self.mgr.expansionsList()
143
 
        descriptions = [self.mgr.description(name) for name in self.expansions]
144
 
        result = ['{0} ({1})'.format(e, d) for e, d in zip(self.expansions, descriptions)]
145
 
        super(ExpansionCompletions, self).__init__(model, result)
146
 
 
147
 
    def executeCompletionItem(self, doc, word, row):
148
 
        self.mgr.doExpand(self.expansions[row], word)
149
 
        return True
150
 
 
151
 
 
152
 
def getCompletions(model, view, word, invocationType):
153
 
    """
154
 
    Returns an object that describes the matches that
155
 
    are useful in the current context.
156
 
    """
157
 
    matches = findMatches(model, view, word, invocationType)
158
 
    if isinstance(matches, CompletionHelper):
159
 
        return matches
160
 
    else:
161
 
        return CompletionList(model, matches)
162
 
        
163
 
def findMatches(model, view, word, invocationType):
164
 
    """
165
 
    Return either a simple list of matches that are useful in the current
166
 
    context, or a CompletionHelper instance that can handle specialized
167
 
    completions itself.
168
 
    """
169
 
    doc = view.document()
170
 
    line, col = word.start().line(), word.start().column()
171
 
    text = doc.line(line)
172
 
    text = text[:col] if text else "" # cause KatePart returns None i.s.o. ""
173
 
    
174
 
    # determine what the user tries to type
175
 
    # very specific situations:
176
 
    if re.search(r'\\(consists|remove)\s*"?$', text):
177
 
        return ly.words.engravers
178
 
    if re.search(r'\bmidiInstrument\s*=\s*#?"$', text):
179
 
        return ly.words.midi_instruments
180
 
    if re.search(r'\\musicglyph\s*#"$', text):
181
 
        return musicglyph_names()
182
 
    if re.search(r'\bmarkFormatter\s*=\s*#$', text):
183
 
        return ly.words.mark_formatters
184
 
    if re.search(r'\\key\s+[a-z]+\s*\\$', text):
185
 
        return ly.words.modes
186
 
    if re.search(r'\\(un)?set\b\s*$', text):
187
 
        return ly.words.contexts + ly.words.contextproperties
188
 
    if re.search(r'\\(new|change|context)\s+$', text):
189
 
        return ly.words.contexts
190
 
    if ly.words.set_context_re.search(text):
191
 
        return ly.words.contextproperties
192
 
    if ly.words.context_re.search(text):
193
 
        return ly.words.grobs
194
 
    if text.endswith("#'"):
195
 
        m = ly.words.grob_re.search(text[:-2])
196
 
        if m:
197
 
            return ly.words.schemeprops(m.group(1))
198
 
        if re.search(r"\\tweak\b\s*$", text[:-2]):
199
 
            return ly.words.schemeprops()
200
 
    if re.search(r"\\(override|revert)\s+$", text):
201
 
        return ly.words.contexts + ly.words.grobs
202
 
    if re.search(r'\\repeat\s+"?$', text):
203
 
        return ly.words.repeat_types
204
 
    if re.search(r'\\clef\s*"$', text):
205
 
        return ly.words.clefs
206
 
    if re.search(r"\\clef\s+$", text):
207
 
        return ly.words.clefs_plain
208
 
    if re.search(r"\bcolor\s*=?\s*#$", text):
209
 
        return ColorCompletions(model, ly.colors.colors_predefined)
210
 
    if re.search(r"\bx11-color\s*'$", text):
211
 
        return ColorCompletions(model, ly.colors.colors_x11)
212
 
    if re.search(r"#'break-visibility\s*=\s*#$", text):
213
 
        return ly.words.break_visibility
214
 
    # parse to get current context
215
 
    fragment = doc.text(KTextEditor.Range(KTextEditor.Cursor(0, 0),
216
 
        word.start())) or "" # KatePart returns None instead of empty string
217
 
    tokenizer = ly.tokenize.Tokenizer()
218
 
    token = None # in case the next loop does not run at all
219
 
    for token in tokenizer.tokens(fragment):
220
 
        pass
221
 
    # don't bother if we are inside a string or comment
222
 
    if isinstance(token, (tokenizer.String, tokenizer.Comment)):
223
 
        return
224
 
    
225
 
    if text.endswith("\\"):
226
 
        if isinstance(tokenizer.parser(), tokenizer.MarkupParser):
227
 
            if tokenizer.parser().token == "\\markuplines":
228
 
                return ly.words.markupcommands + ly.words.markuplistcommands
229
 
            else:
230
 
                return ly.words.markupcommands
231
 
        commands = (ly.words.keywords + ly.words.keywords_completion
232
 
            + ly.words.musiccommands + ly.words.musiccommands_completion
233
 
            + lilyPondVersion())
234
 
        if tokenizer.parser().token == "\\context":
235
 
            return commands + ly.words.contexts
236
 
        else:
237
 
            return commands
238
 
 
239
 
    if isinstance(tokenizer.parser(), tokenizer.SchemeParser):
240
 
        # is the last token the scheme-introducing '#' ?
241
 
        if token is tokenizer.parser().token:
242
 
            return ('UP', 'DOWN', 'CENTER', 'LEFT', 'RIGHT')
243
 
        else:
244
 
            if text.endswith("#("):
245
 
                if tokenizer.parser(-2).token == "\\paper":
246
 
                    return ('set-paper-size',)
247
 
            elif text.endswith("#:"):
248
 
                return ly.words.markupcommands
249
 
            elif text.endswith("#(set-accidental-style '"):
250
 
                return ly.words.accidentalstyles
251
 
            return ly.words.schemefuncs
252
 
        
253
 
    if col == 0 or text[-1] in " \t":
254
 
        # all kinds of variables only at start of line or after whitespace
255
 
        # the VarCompletions model can add ' = ' after them
256
 
        if tokenizer.parser().token == "\\header":
257
 
            return VarCompletions(model, ly.words.headervars)
258
 
        if tokenizer.parser().token == "\\paper":    
259
 
            return VarCompletions(model, ly.words.papervars)
260
 
        if tokenizer.parser().token == "\\layout":
261
 
            return VarCompletions(model, ly.words.layoutvars)
262
 
        if tokenizer.parser().token in ("\\context", "\\with"):
263
 
            return VarCompletions(model, ly.words.contextproperties)
264
 
    
265
 
    if not text.strip():
266
 
         # only on an empty line: show the expansions
267
 
         return ExpansionCompletions(model)
268
 
 
269
 
 
270
 
# load some (cached) data
271
 
def musicglyph_names():
272
 
    font = ly.version.LilyPondInstance(lilyPondCommand()).fontInfo("emmentaler-20")
273
 
    if font:
274
 
        return tuple(font.glyphs())
275
 
    return ()
276
 
 
277
 
def lilyPondVersion():
278
 
    ver = frescobaldi_app.version.defaultVersion()
279
 
    return ('version "{0}"'.format(ver),) if ver else ()
280