Coverage for C:\Repos\leo-editor\leo\external\codewise.py: 15%
434 statements
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
« prev ^ index » next coverage.py v6.4, created at 2022-05-24 10:21 -0500
1#!/usr/bin/env python
2#@+leo-ver=5-thin
3#@+node:ekr.20110310091639.14254: * @file ../external/codewise.py
4#@@first
5#@+<< docstring >>
6#@+node:ekr.20110310091639.14291: ** << docstring >>
7r""" CodeWise - global code intelligence database
9Why this module
10===============
12- Exuberant ctags is an excellent code scanner
13- Unfortunately, TAGS file lookup sucks for "find methods of this class"
14- TAGS files can be all around the hard drive. CodeWise database is
15 just one file (by default ~/.codewise.db)
16- I wanted to implement modern code completion for Leo editor
17- codewise.py is usable as a python module, or a command line tool.
19Creating ctags data
20===================
221. Make sure you have exuberant ctags (not just regular ctags)
23 installed. It's an Ubuntu package, so its easy to install if
24 you're using Ubuntu.
272. [Optional] Create a custom ~/.ctags file containing default
28 configuration settings for ctags. See:
29 http://ctags.sourceforge.net/ctags.html#FILES for more
30 details.
32 The ``codewise setup`` command (see below), will leave this
33 file alone if it exists; otherwise, ``codewise setup`` will
34 create a ~/.ctags file containing::
36 --exclude=*.html
37 --exclude=*.css
393. Create the ctags data in ~/.codewise.db using this module. Execute the
40 following from a console window::
42 codewise setup
43 # Optional: creates ~/.ctags if it does not exist.
44 # See http://ctags.sourceforge.net/ctags.html#FILES
45 codewise init
46 # Optional: deletes ~/.codewise.db if it exists.
47 codewise parse <path to directory>
48 # Adds ctags data to ~/.codewise.db for <directory>
50**Note**: On Windows, use a batch file, say codewise.bat, to execute the
51above code. codewise.bat contains::
53 python <path to leo>\leo\external\codewise.py %*
55Using the autocompleter
56=======================
58After restarting Leo, type, for example, in the body pane::
60 c.op<ctrl-space>
62that is, use use the autocomplete-force command,
63to find all the c. methods starting with 'op' etc.
65Theory of operation
66===================
68- ~/.codewise.db is an sqlite database with following tables:
70CLASS maps class id's to names.
72FILE maps file id's to file names
74DATASOURCE contains places where data has been parsed from, to enable reparse
76FUNCTION, the most important one, contains functions/methods, along with CLASS
77 and FILE it was found in. Additionally, it has SEARCHPATTERN field that can be
78 used to give calltips, or used as a regexp to find the method from file
79 quickly.
81You can browse the data by installing sqlitebrovser and doing 'sqlitebrowser
82~/codewise.db'
84If you know the class name you want to find the methods for,
85CodeWise.get_members with a list of classes to match.
87If you want to match a function without a class, call CodeWise.get_functions.
88This can be much slower if you have a huge database.
90"""
91#@-<< docstring >>
92#@+<< imports >>
93#@+node:ekr.20110310091639.14293: ** << imports >>
94import os
95import sys
96import sqlite3
97from sqlite3 import ProgrammingError
98import traceback
99from typing import List
101#@-<< imports >>
102consoleEncoding = None
103#@+<< define usage >>
104#@+node:ekr.20110310091639.14292: ** << define usage >>
105usage = """
106codewise setup
107 (Optional - run this first to create template ~/.ctags)
109codewise init
110 Create/recreate the global database
112codewise parse /my/project /other/project
113 Parse specified directories (with recursion) and add results to db
115codewise m
116 List all classes
118codewise m MyClass
119 Show all methods in MyClass
121codewise f PREFIX
122 Show all symbols (also nonmember functiosn) starting with PREFIX.
123 PREFIX can be omitted to get a list of all symbols
125codewise parseall
126 Clear database, reparse all paths previously added by 'codewise parse'
128codewise sciapi pyqt.api
129 Parse an api file (as supported by scintilla, eric4...)
131Commands you don't probably need:
133codewise tags TAGS
134 Dump already-created tagfile TAGS to database
136"""
137#@-<< define usage >>
138#@+<< define DB_SCHEMA >>
139#@+node:ekr.20110310091639.14255: ** << define DB_SCHEMA >>
140DB_SCHEMA = """
141BEGIN TRANSACTION;
142CREATE TABLE class (id INTEGER PRIMARY KEY, file INTEGER, name TEXT, searchpattern TEXT);
143CREATE TABLE file (id INTEGER PRIMARY KEY, path TEXT);
144CREATE TABLE function (id INTEGER PRIMARY KEY, class INTEGER, file INTEGER, name TEXT, searchpattern TEXT);
145CREATE TABLE datasource (type TEXT, src TEXT);
147CREATE INDEX idx_class_name ON class(name ASC);
148CREATE INDEX idx_function_class ON function(class ASC);
150COMMIT;
151"""
152#@-<< define DB_SCHEMA >>
153DEFAULT_DB = os.path.normpath(os.path.expanduser("~/.codewise.db"))
154# print('default db: %s' % DEFAULT_DB)
155#@+others
156#@+node:ekr.20110310091639.14295: ** top level...
157#@+node:ekr.20110310091639.14294: *3* codewise cmd wrappers
158#@+node:ekr.20110310091639.14289: *4* cmd_functions
159def cmd_functions(args):
160 cw = CodeWise()
161 if args:
162 funcs = cw.get_functions(args[0])
163 else:
164 funcs = cw.get_functions()
165 lines = list(set(el[0] + "\t" + el[1] for el in funcs))
166 lines.sort()
167 return lines # EKR
168#@+node:ekr.20110310091639.14285: *4* cmd_init
169def cmd_init(args):
170 print("Initializing CodeWise db at: %s" % DEFAULT_DB)
171 if os.path.isfile(DEFAULT_DB):
172 os.remove(DEFAULT_DB)
173 CodeWise()
174#@+node:ekr.20110310091639.14288: *4* cmd_members
175def cmd_members(args):
176 cw = CodeWise()
177 if args:
178 mems = cw.get_members([args[0]])
179 lines = list(set(el + "\t" + pat for el, pat in mems))
180 else:
181 lines = cw.classcache.keys() # type:ignore
182 lines.sort()
183 return lines # EKR
184#@+node:ekr.20110310091639.14283: *4* cmd_parse
185def cmd_parse(args):
186 assert args
187 cw = CodeWise()
188 cw.parse(args)
189#@+node:ekr.20110310091639.14282: *4* cmd_parseall
190def cmd_parseall(args):
191 cw = CodeWise()
192 cw.parseall()
193#@+node:ekr.20110310091639.14281: *4* cmd_scintilla
194def cmd_scintilla(args):
195 cw = CodeWise()
196 for fil in args:
197 f = open(fil)
198 cw.feed_scintilla(f)
199 f.close()
200#@+node:ekr.20110310091639.14286: *4* cmd_setup
201def cmd_setup(args):
203 ctagsfile = os.path.normpath(os.path.expanduser("~/.ctags"))
204 if os.path.isfile(ctagsfile):
205 print("Using template file: %s" % ctagsfile)
206 else:
207 print("Creating template: %s" % ctagsfile)
208 open(ctagsfile, "w").write("--exclude=*.html\n--exclude=*.css\n")
209 # No need for this: the docs say to run "init" after "setup"
210 # cmd_init(args)
211#@+node:ekr.20110310091639.14284: *4* cmd_tags
212def cmd_tags(args):
213 cw = CodeWise()
214 cw.feed_ctags(open(args[0]))
215#@+node:ekr.20110310093050.14234: *3* functions from leoGlobals
216#@+node:ekr.20110310093050.14291: *4* Most common functions... (codewise.py)
217#@+node:ekr.20110310093050.14296: *5* callers & _callerName (codewise)
218def callers(n=4, count=0, excludeCaller=True, files=False):
219 '''Return a list containing the callers of the function that called callerList.
221 If the excludeCaller keyword is True (the default), callers is not on the list.
223 If the files keyword argument is True, filenames are included in the list.
224 '''
225 # sys._getframe throws ValueError in both cpython and jython if there are less than i entries.
226 # The jython stack often has less than 8 entries,
227 # so we must be careful to call _callerName with smaller values of i first.
228 result = []
229 i = 3 if excludeCaller else 2
230 while 1:
231 s = _callerName(i, files=files)
232 if s:
233 result.append(s)
234 if not s or len(result) >= n: break
235 i += 1
236 result.reverse()
237 if count > 0: result = result[:count]
238 sep = '\n' if files else ','
239 return sep.join(result)
240#@+node:ekr.20110310093050.14297: *6* _callerName
241def _callerName(n=1, files=False):
242 try: # get the function name from the call stack.
243 f1 = sys._getframe(n) # The stack frame, n levels up.
244 code1 = f1.f_code # The code object
245 name = code1.co_name
246 if name == '__init__':
247 name = '__init__(%s,line %s)' % (
248 shortFileName(code1.co_filename), code1.co_firstlineno)
249 return '%s:%s' % (shortFileName(code1.co_filename), name) if files else name
250 except ValueError:
251 return '' # The stack is not deep enough.
252 except Exception:
253 es_exception()
254 return '' # "<no caller name>"
255#@+node:ekr.20110310093050.14253: *5* doKeywordArgs (codewise)
256def doKeywordArgs(keys, d=None):
257 '''Return a result dict that is a copy of the keys dict
258 with missing items replaced by defaults in d dict.'''
259 if d is None: d = {}
260 result = {}
261 for key, default_val in d.items():
262 isBool = default_val in (True, False)
263 val = keys.get(key)
264 if isBool and val in (True, 'True', 'true'):
265 result[key] = True
266 elif isBool and val in (False, 'False', 'false'):
267 result[key] = False
268 elif val is None:
269 result[key] = default_val
270 else:
271 result[key] = val
272 return result
273#@+node:ekr.20180311191907.1: *5* error (codewise)
274def error(*args, **keys):
275 print(args, keys)
276#@+node:ekr.20180311192928.1: *5* es_exception (codewise)
277def es_exception(full=True, c=None, color="red"):
278 typ, val, tb = sys.exc_info()
279 # val is the second argument to the raise statement.
280 if full:
281 lines = traceback.format_exception(typ, val, tb)
282 else:
283 lines = traceback.format_exception_only(typ, val)
284 for line in lines:
285 print(line)
286 fileName, n = getLastTracebackFileAndLineNumber()
287 return fileName, n
288#@+node:ekr.20180311193048.1: *5* getLastTracebackFileAndLineNumber (codewise)
289def getLastTracebackFileAndLineNumber():
290 typ, val, tb = sys.exc_info()
291 if typ == SyntaxError:
292 # IndentationError is a subclass of SyntaxError.
293 # Much easier in Python 2.6 and 3.x.
294 return val.filename, val.lineno # type:ignore
295 #
296 # Data is a list of tuples, one per stack entry.
297 # Tupls have the form (filename,lineNumber,functionName,text).
298 data = traceback.extract_tb(tb)
299 if data:
300 item = data[-1] # Get the item at the top of the stack.
301 filename, n, functionName, text = item
302 return filename, n
303 #
304 # Should never happen.
305 return '<string>', 0
306#@+node:ekr.20110310093050.14293: *5* pdb (codewise)
307def pdb(message=''):
308 """Fall into pdb."""
309 import pdb # Required: we have just defined pdb as a function!
310 if message:
311 print(message)
312 pdb.set_trace()
313#@+node:ekr.20110310093050.14263: *5* pr (codewise)
314# see: http://www.diveintopython.org/xml_processing/unicode.html
316def pr(*args, **keys): # (codewise!)
317 '''Print all non-keyword args, and put them to the log pane.
318 The first, third, fifth, etc. arg translated by translateString.
319 Supports color, comma, newline, spaces and tabName keyword arguments.
320 '''
321 # Compute the effective args.
322 d = {'commas': False, 'newline': True, 'spaces': True}
323 d = doKeywordArgs(keys, d)
324 newline = d.get('newline')
325 if getattr(sys.stdout, 'encoding', None):
326 # sys.stdout is a TextIOWrapper with a particular encoding.
327 encoding = sys.stdout.encoding
328 else:
329 encoding = 'utf-8'
330 # Translates everything to unicode.
331 s = translateArgs(args, d)
332 s = toUnicode(s, encoding=encoding, reportErrors=False)
333 if newline:
334 s += u('\n')
335 # Python's print statement *can* handle unicode, but
336 # sitecustomize.py must have sys.setdefaultencoding('utf-8')
337 sys.stdout.write(s) # Unit tests do not change sys.stdout.
338#@+node:ekr.20180311193230.1: *5* shortFileName (codewise)
339def shortFileName(fileName, n=None):
340 '''Return the base name of a path.'''
341 # pylint: disable=invalid-unary-operand-type
342 if not fileName:
343 return ''
344 if n is None or n < 1:
345 return os.path.basename(fileName)
346 return '/'.join(fileName.replace('\\', '/').split('/')[-n :])
347#@+node:ekr.20110310093050.14268: *5* trace (codewise)
348# Convert all args to strings.
350def trace(*args, **keys):
351 # Compute the effective args.
352 d = {'align': 0, 'newline': True}
353 d = doKeywordArgs(keys, d)
354 newline = d.get('newline')
355 align = d.get('align')
356 if align is None: align = 0
357 # Compute the caller name.
358 try: # get the function name from the call stack.
359 f1 = sys._getframe(1) # The stack frame, one level up.
360 code1 = f1.f_code # The code object
361 name = code1.co_name # The code name
362 except Exception:
363 name = ''
364 if name == "?":
365 name = "<unknown>"
366 # Pad the caller name.
367 if align != 0 and len(name) < abs(align):
368 pad = ' ' * (abs(align) - len(name))
369 if align > 0: name = name + pad
370 else: name = pad + name
371 # Munge *args into s.
372 result = [name]
373 for arg in args:
374 if isString(arg):
375 pass
376 elif isBytes(arg):
377 arg = toUnicode(arg)
378 else:
379 arg = repr(arg)
380 if result:
381 result.append(" " + arg)
382 else:
383 result.append(arg)
384 s = ''.join(result)
385 # 'print s,' is not valid syntax in Python 3.x.
386 pr(s, newline=newline)
387#@+node:ekr.20110310093050.14264: *5* translateArgs (codewise)
388def translateArgs(args, d):
389 '''Return the concatenation of all args, with odd args translated.'''
390 global consoleEncoding
391 if not consoleEncoding:
392 e = sys.getdefaultencoding()
393 consoleEncoding = e if isValidEncoding(e) else 'utf-8'
394 result: List[str] = []
395 n = 0
396 spaces = d.get('spaces')
397 for arg in args:
398 n += 1
399 # print('translateArgs: arg',arg,type(arg),isString(arg),'will trans',(n%2)==1)
400 # First, convert to unicode.
401 if isString(arg):
402 arg = toUnicode(arg, consoleEncoding)
403 # Just do this for the stand-alone version.
404 if not isString(arg):
405 arg = repr(arg)
406 if arg:
407 if result and spaces: result.append(' ')
408 result.append(arg)
409 return ''.join(result)
410#@+node:ekr.20110310093050.14280: *4* Unicode utils (codewise)...
411#@+node:ekr.20110310093050.14282: *5* isBytes, isCallable, isString & isUnicode (codewise)
412# The syntax of these functions must be valid on Python2K and Python3K.
414# Codewise
416def isBytes(s):
417 '''Return True if s is Python3k bytes type.'''
418 return isinstance(s, bytes)
420def isCallable(obj):
421 return hasattr(obj, '__call__')
423def isString(s):
424 '''Return True if s is any string, but not bytes.'''
425 return isinstance(s, str)
427def isUnicode(s):
428 '''Return True if s is a unicode string.'''
429 return isinstance(s, str)
431#@+node:ekr.20110310093050.14283: *5* isValidEncoding (codewise)
432def isValidEncoding(encoding):
433 if not encoding:
434 return False
435 if sys.platform == 'cli':
436 return True
437 import codecs
438 try:
439 codecs.lookup(encoding)
440 return True
441 except LookupError: # Windows.
442 return False
443 except AttributeError: # Linux.
444 return False
445#@+node:ekr.20110310093050.14286: *5* toEncodedString (codewise)
446def toEncodedString(s, encoding='utf-8', reportErrors=False):
447 '''Convert unicode string to an encoded string.'''
448 if not isUnicode(s):
449 return s
450 if encoding is None:
451 encoding = 'utf-8'
452 try:
453 s = s.encode(encoding, "strict")
454 except UnicodeError:
455 s = s.encode(encoding, "replace")
456 if reportErrors:
457 error("Error converting %s from unicode to %s encoding" % (s, encoding))
458 return s
459#@+node:ekr.20110310093050.14287: *5* toUnicode (codewise)
460def toUnicode(s, encoding='utf-8', reportErrors=False):
461 '''Connvert a non-unicode string with the given encoding to unicode.'''
462 if isUnicode(s):
463 return s
464 if not encoding:
465 encoding = 'utf-8'
466 try:
467 s = s.decode(encoding, 'strict')
468 except UnicodeError:
469 s = s.decode(encoding, 'replace')
470 if reportErrors:
471 error("Error converting %s from %s encoding to unicode" % (s, encoding))
472 return s
473#@+node:ekr.20110310093050.14288: *5* u & ue (codewise)
474def u(s):
475 return s
477def ue(s, encoding):
478 return s if isUnicode(s) else str(s, encoding)
479#@+node:ekr.20110310091639.14290: *3* main
480def main():
482 if len(sys.argv) < 2:
483 print(usage)
484 return
485 cmd = sys.argv[1]
486 # print "cmd",cmd
487 args = sys.argv[2:]
488 if cmd == 'tags':
489 cmd_tags(args)
490 elif cmd == 'm':
491 printlines(cmd_members(args))
492 elif cmd == 'f':
493 printlines(cmd_functions(args))
494 elif cmd == 'parse':
495 cmd_parse(args)
496 elif cmd == 'parseall':
497 cmd_parseall(args)
498 elif cmd == 'sciapi':
499 cmd_scintilla(args)
500 elif cmd == 'init':
501 cmd_init(args)
502 elif cmd == 'setup':
503 cmd_setup(args)
504#@+node:ekr.20110310091639.14287: *3* printlines
505def printlines(lines):
506 for l in lines:
507 try:
508 print(l)
509 except Exception: # EKR: UnicodeEncodeError:
510 pass
511#@+node:ekr.20110310091639.14280: *3* run_ctags
512def run_ctags(paths):
513 cm = 'ctags -R --sort=no -f - ' + " ".join(paths)
514 # print(cm)
515 f = os.popen(cm)
516 return f
517#@+node:ekr.20110310091639.14296: *3* test
518def test(self):
519 pass
520#@+node:ekr.20110310091639.14256: ** class CodeWise
521class CodeWise:
522 #@+others
523 #@+node:ekr.20110310091639.14257: *3* __init__(CodeWise)
524 def __init__(self, dbpath=None):
525 if dbpath is None:
526 # use "current" db from env var
527 dbpath = DEFAULT_DB
528 # print(dbpath)
529 self.reset_caches()
530 if not os.path.exists(dbpath):
531 self.createdb(dbpath)
532 else:
533 self.dbconn = sqlite3.connect(dbpath)
534 self.create_caches()
535 #@+node:ekr.20110310091639.14258: *3* createdb
536 def createdb(self, dbpath):
537 self.dbconn = c = sqlite3.connect(dbpath)
538 # print(self.dbconn)
539 c.executescript(DB_SCHEMA)
540 c.commit()
541 c.close()
542 #@+node:ekr.20110310091639.14259: *3* create_caches
543 def create_caches(self):
544 """ read existing db and create caches """
545 c = self.cursor()
546 c.execute('select id, name from class')
547 for idd, name in c:
548 self.classcache[name] = idd
549 c.execute('select id, path from file')
550 for idd, name in c:
551 self.filecache[name] = idd
552 c.close()
553 #print self.classcache
554 #@+node:ekr.20110310091639.14260: *3* reset_caches
555 def reset_caches(self):
556 self.classcache = {}
557 self.filecache = {}
558 self.fileids_scanned = set()
559 #@+node:ekr.20110310091639.14261: *3* cursor
560 def cursor(self):
561 if self.dbconn:
562 try:
563 return self.dbconn.cursor()
564 except ProgrammingError:
565 print("No cursor for codewise DB, closed database?")
566 return None
567 #@+node:ekr.20110310091639.14262: *3* class_id
568 def class_id(self, classname):
569 """ return class id. May create new class """
570 if classname is None:
571 return 0
572 idd = self.classcache.get(classname)
573 if idd is None:
574 c = self.cursor()
575 c.execute('insert into class(name) values (?)', [classname])
576 c.close()
577 idd = c.lastrowid
578 self.classcache[classname] = idd
579 return idd
580 #@+node:ekr.20110310091639.14263: *3* get_members
581 def get_members(self, classnames):
582 clset = set(classnames)
583 # class_by_id = dict((v, k) for k, v in self.classcache.items())
584 # file_by_id = dict((v, k) for k, v in self.filecache.items())
585 result = []
586 for name, idd in self.classcache.items():
587 if name in clset:
588 c = self.cursor()
589 c.execute(
590 'select name, class, file, searchpattern from function where class = (?)',
591 (idd,))
592 for name, klassid, fileid, pat in c:
593 result.append((name, pat))
594 return result
595 #@+node:ekr.20110310091639.14264: *3* get_functions
596 def get_functions(self, prefix=None):
597 c = self.cursor()
598 if prefix is None:
599 c.execute('select name, class, file, searchpattern from function')
600 else:
601 prefix = str(prefix)
602 c.execute(
603 'select name, class, file, searchpattern from function where name like (?)', (
604 prefix + '%',))
605 return [(name, pat, klassid, fileid) for name, klassid, fileid, pat in c]
606 #@+node:ekr.20110310091639.14265: *3* file_id
607 def file_id(self, fname):
608 if fname == '':
609 return 0
610 idd = self.filecache.get(fname)
611 if idd is None:
612 c = self.cursor()
613 c.execute('insert into file(path) values (?)', [fname])
614 idd = c.lastrowid
615 self.filecache[fname] = idd
616 self.fileids_scanned.add(idd)
617 else:
618 if idd in self.fileids_scanned:
619 return idd
620 # we are rescanning a file with old entries - nuke old entries
621 #print "rescan", fname
622 c = self.cursor()
623 c.execute("delete from function where file = (?)", (idd,))
624 #self.dbconn.commit()
625 self.fileids_scanned.add(idd)
626 return idd
627 #@+node:ekr.20110310091639.14266: *3* feed_function
628 def feed_function(self, func_name, class_name, file_name, aux):
629 """ insert one function
631 'aux' can be a search pattern (as with ctags), signature, or description
634 """
635 clid = self.class_id(class_name)
636 fid = self.file_id(file_name)
637 c = self.cursor()
638 c.execute(
639 'insert into function(class, name, searchpattern, file) values (?, ?, ?, ?)',
640 [clid, func_name, aux, fid])
641 #@+node:ekr.20110310091639.14267: *3* feed_scintilla
642 def feed_scintilla(self, apifile_obj):
643 """ handle scintilla api files
645 Syntax is like:
647 qt.QApplication.style?4() -> QStyle
648 """
649 for l in apifile_obj:
650 parts = l.split('?')
651 fullsym = parts[0].rsplit('.', 1)
652 klass, func = fullsym
653 if len(parts) == 2:
654 desc = parts[1]
655 else:
656 desc = ''
657 # now our class is like qt.QApplication. We do the dirty trick and
658 # remove all but actual class name
659 shortclass = klass.rsplit('.', 1)[-1]
660 #print func, klass, desc
661 self.feed_function(func.strip(), shortclass.strip(), '', desc.strip())
662 self.dbconn.commit()
663 #@+node:ekr.20110310091639.14268: *3* feed_ctags
664 def feed_ctags(self, tagsfile_obj):
665 for l in tagsfile_obj:
666 if l.startswith('!'):
667 continue
668 fields = l.split('\t')
669 m = fields[0]
670 fil = fields[1]
671 pat = fields[2]
672 # typ = fields[3]
673 klass = None
674 try:
675 ext = fields[4]
676 if ext and ext.startswith('class:'):
677 klass = ext.split(':', 1)[1].strip()
678 idd = self.class_id(klass)
679 #print "klass",klass, idd
680 except IndexError:
681 ext = None
682 # class id 0 = function
683 idd = 0
684 c = self.cursor()
685 #print fields
686 fid = self.file_id(fil)
687 c.execute(
688 'insert into function(class, name, searchpattern, file) values (?, ?, ?, ?)',
689 [idd, m, pat, fid])
690 self.dbconn.commit()
691 #c.commit()
692 #print fields
693 #@+node:ekr.20110310091639.14269: *3* add_source
694 def add_source(self, type, src):
695 c = self.cursor()
696 c.execute('insert into datasource(type, src) values (?,?)', (type, src))
697 self.dbconn.commit()
698 #@+node:ekr.20110310091639.14270: *3* sources
699 def sources(self):
700 c = self.cursor()
701 c.execute('select type, src from datasource')
702 return list(c)
703 #@+node:ekr.20110310091639.14271: *3* zap_symbols
704 def zap_symbols(self):
705 c = self.cursor()
706 tables = ['class', 'file', 'function']
707 for t in tables:
708 c.execute('delete from ' + t)
709 self.dbconn.commit()
710 #@+node:ekr.20110310091639.14272: *3* # high level commands
711 # high level commands
712 #@+node:ekr.20110310091639.14273: *3* parseall
713 def parseall(self):
714 sources = self.sources()
715 self.reset_caches()
716 self.zap_symbols()
717 tagdirs = [td for typ, td in sources if typ == 'tagdir']
718 self.parse(tagdirs)
719 self.dbconn.commit()
720 #@+node:ekr.20110310091639.14274: *3* parse
721 def parse(self, paths):
722 paths = set(os.path.abspath(p) for p in paths)
723 f = run_ctags(paths)
724 self.feed_ctags(f)
725 sources = self.sources()
726 for a in paths:
727 if ('tagdir', a) not in sources:
728 self.add_source('tagdir', a)
729 #@-others
730#@+node:ekr.20110310091639.14275: ** class ContextSniffer
731class ContextSniffer:
732 """ Class to analyze surrounding context and guess class
734 For simple dynamic code completion engines
736 """
737 #@+others
738 #@+node:ekr.20110310091639.14276: *3* __init__ (ContextSniffer)
739 def __init__(self):
740 # var name => list of classes
741 self.vars = {}
742 #@+node:ekr.20110310091639.14277: *3* declare
743 def declare(self, var, klass):
744 # print("declare",var,klass)
745 vars = self.vars.get(var, [])
746 if not vars:
747 self.vars[var] = vars
748 vars.append(klass)
749 #@+node:ekr.20110310091639.14278: *3* push_declarations
750 def push_declarations(self, body):
751 for l in body.splitlines():
752 l = l.lstrip()
753 if not l.startswith('#'):
754 continue
755 l = l.lstrip('#')
756 parts = l.strip(':')
757 if len(parts) != 2:
758 continue
759 self.declare(parts[0].strip(), parts[1].strip())
760 #@+node:ekr.20110310091639.14279: *3* set_small_context
761 def set_small_context(self, body):
762 """ Set immediate function """
763 self.push_declarations(body)
764 #@-others
765#@-others
766#@@language python
767#@@tabwidth -4
768#@@pagewidth 70
770if __name__ == "__main__":
771 main()
772#@-leo