Coverage for C:\leo.repo\leo-editor\leo\external\codewise.py : 15%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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 s = translateArgs(args, d)
331 # Translates everything to unicode.
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)
338 # Codewise: unit tests do not change sys.stdout.
339#@+node:ekr.20180311193230.1: *5* shortFileName (codewise)
340def shortFileName(fileName, n=None):
341 '''Return the base name of a path.'''
342 # pylint: disable=invalid-unary-operand-type
343 if not fileName:
344 return ''
345 if n is None or n < 1:
346 return os.path.basename(fileName)
347 return '/'.join(fileName.replace('\\', '/').split('/')[-n :])
348#@+node:ekr.20110310093050.14268: *5* trace (codewise)
349# Convert all args to strings.
351def trace(*args, **keys):
352 # Compute the effective args.
353 d = {'align': 0, 'newline': True}
354 d = doKeywordArgs(keys, d)
355 newline = d.get('newline')
356 align = d.get('align')
357 if align is None: align = 0
358 # Compute the caller name.
359 try: # get the function name from the call stack.
360 f1 = sys._getframe(1) # The stack frame, one level up.
361 code1 = f1.f_code # The code object
362 name = code1.co_name # The code name
363 except Exception:
364 name = ''
365 if name == "?":
366 name = "<unknown>"
367 # Pad the caller name.
368 if align != 0 and len(name) < abs(align):
369 pad = ' ' * (abs(align) - len(name))
370 if align > 0: name = name + pad
371 else: name = pad + name
372 # Munge *args into s.
373 result = [name]
374 for arg in args:
375 if isString(arg):
376 pass
377 elif isBytes(arg):
378 arg = toUnicode(arg)
379 else:
380 arg = repr(arg)
381 if result:
382 result.append(" " + arg)
383 else:
384 result.append(arg)
385 s = ''.join(result)
386 # 'print s,' is not valid syntax in Python 3.x.
387 pr(s, newline=newline)
388#@+node:ekr.20110310093050.14264: *5* translateArgs (codewise)
389def translateArgs(args, d):
390 '''Return the concatenation of all args, with odd args translated.'''
391 global consoleEncoding
392 if not consoleEncoding:
393 e = sys.getdefaultencoding()
394 consoleEncoding = e if isValidEncoding(e) else 'utf-8'
395 result: List[str] = []
396 n = 0
397 spaces = d.get('spaces')
398 for arg in args:
399 n += 1
400 # print('translateArgs: arg',arg,type(arg),isString(arg),'will trans',(n%2)==1)
401 # First, convert to unicode.
402 if isString(arg):
403 arg = toUnicode(arg, consoleEncoding)
404 # Just do this for the stand-alone version.
405 if not isString(arg):
406 arg = repr(arg)
407 if arg:
408 if result and spaces: result.append(' ')
409 result.append(arg)
410 return ''.join(result)
411#@+node:ekr.20110310093050.14280: *4* Unicode utils (codewise)...
412#@+node:ekr.20110310093050.14282: *5* isBytes, isCallable, isString & isUnicode (codewise)
413# The syntax of these functions must be valid on Python2K and Python3K.
415# Codewise
417def isBytes(s):
418 '''Return True if s is Python3k bytes type.'''
419 return isinstance(s, bytes)
421def isCallable(obj):
422 return hasattr(obj, '__call__')
424def isString(s):
425 '''Return True if s is any string, but not bytes.'''
426 return isinstance(s, str)
428def isUnicode(s):
429 '''Return True if s is a unicode string.'''
430 return isinstance(s, str)
432#@+node:ekr.20110310093050.14283: *5* isValidEncoding (codewise)
433def isValidEncoding(encoding):
434 if not encoding:
435 return False
436 if sys.platform == 'cli':
437 return True
438 import codecs
439 try:
440 codecs.lookup(encoding)
441 return True
442 except LookupError: # Windows.
443 return False
444 except AttributeError: # Linux.
445 return False
446#@+node:ekr.20110310093050.14286: *5* toEncodedString (codewise)
447def toEncodedString(s, encoding='utf-8', reportErrors=False):
448 '''Convert unicode string to an encoded string.'''
449 if not isUnicode(s):
450 return s
451 if encoding is None:
452 encoding = 'utf-8'
453 try:
454 s = s.encode(encoding, "strict")
455 except UnicodeError:
456 s = s.encode(encoding, "replace")
457 if reportErrors:
458 error("Error converting %s from unicode to %s encoding" % (s, encoding))
459 return s
460#@+node:ekr.20110310093050.14287: *5* toUnicode (codewise)
461def toUnicode(s, encoding='utf-8', reportErrors=False):
462 '''Connvert a non-unicode string with the given encoding to unicode.'''
463 if isUnicode(s):
464 return s
465 if not encoding:
466 encoding = 'utf-8'
467 try:
468 s = s.decode(encoding, 'strict')
469 except UnicodeError:
470 s = s.decode(encoding, 'replace')
471 if reportErrors:
472 error("Error converting %s from %s encoding to unicode" % (s, encoding))
473 return s
474#@+node:ekr.20110310093050.14288: *5* u & ue (codewise)
475def u(s):
476 return s
478def ue(s, encoding):
479 return s if isUnicode(s) else str(s, encoding)
480#@+node:ekr.20110310091639.14290: *3* main
481def main():
483 if len(sys.argv) < 2:
484 print(usage)
485 return
486 cmd = sys.argv[1]
487 # print "cmd",cmd
488 args = sys.argv[2:]
489 if cmd == 'tags':
490 cmd_tags(args)
491 elif cmd == 'm':
492 printlines(cmd_members(args))
493 elif cmd == 'f':
494 printlines(cmd_functions(args))
495 elif cmd == 'parse':
496 cmd_parse(args)
497 elif cmd == 'parseall':
498 cmd_parseall(args)
499 elif cmd == 'sciapi':
500 cmd_scintilla(args)
501 elif cmd == 'init':
502 cmd_init(args)
503 elif cmd == 'setup':
504 cmd_setup(args)
505#@+node:ekr.20110310091639.14287: *3* printlines
506def printlines(lines):
507 for l in lines:
508 try:
509 print(l)
510 except Exception: # EKR: UnicodeEncodeError:
511 pass
512#@+node:ekr.20110310091639.14280: *3* run_ctags
513def run_ctags(paths):
514 cm = 'ctags -R --sort=no -f - ' + " ".join(paths)
515 # print(cm)
516 f = os.popen(cm)
517 return f
518#@+node:ekr.20110310091639.14296: *3* test
519def test(self):
520 pass
521#@+node:ekr.20110310091639.14256: ** class CodeWise
522class CodeWise:
523 #@+others
524 #@+node:ekr.20110310091639.14257: *3* __init__(CodeWise)
525 def __init__(self, dbpath=None):
526 if dbpath is None:
527 # use "current" db from env var
528 dbpath = DEFAULT_DB
529 # print(dbpath)
530 self.reset_caches()
531 if not os.path.exists(dbpath):
532 self.createdb(dbpath)
533 else:
534 self.dbconn = sqlite3.connect(dbpath)
535 self.create_caches()
536 #@+node:ekr.20110310091639.14258: *3* createdb
537 def createdb(self, dbpath):
538 self.dbconn = c = sqlite3.connect(dbpath)
539 # print(self.dbconn)
540 c.executescript(DB_SCHEMA)
541 c.commit()
542 c.close()
543 #@+node:ekr.20110310091639.14259: *3* create_caches
544 def create_caches(self):
545 """ read existing db and create caches """
546 c = self.cursor()
547 c.execute('select id, name from class')
548 for idd, name in c:
549 self.classcache[name] = idd
550 c.execute('select id, path from file')
551 for idd, name in c:
552 self.filecache[name] = idd
553 c.close()
554 #print self.classcache
555 #@+node:ekr.20110310091639.14260: *3* reset_caches
556 def reset_caches(self):
557 self.classcache = {}
558 self.filecache = {}
559 self.fileids_scanned = set()
560 #@+node:ekr.20110310091639.14261: *3* cursor
561 def cursor(self):
562 if self.dbconn:
563 try:
564 return self.dbconn.cursor()
565 except ProgrammingError:
566 print("No cursor for codewise DB, closed database?")
567 return None
568 #@+node:ekr.20110310091639.14262: *3* class_id
569 def class_id(self, classname):
570 """ return class id. May create new class """
571 if classname is None:
572 return 0
573 idd = self.classcache.get(classname)
574 if idd is None:
575 c = self.cursor()
576 c.execute('insert into class(name) values (?)', [classname])
577 c.close()
578 idd = c.lastrowid
579 self.classcache[classname] = idd
580 return idd
581 #@+node:ekr.20110310091639.14263: *3* get_members
582 def get_members(self, classnames):
583 clset = set(classnames)
584 # class_by_id = dict((v, k) for k, v in self.classcache.items())
585 # file_by_id = dict((v, k) for k, v in self.filecache.items())
586 result = []
587 for name, idd in self.classcache.items():
588 if name in clset:
589 c = self.cursor()
590 c.execute(
591 'select name, class, file, searchpattern from function where class = (?)',
592 (idd,))
593 for name, klassid, fileid, pat in c:
594 result.append((name, pat))
595 return result
596 #@+node:ekr.20110310091639.14264: *3* get_functions
597 def get_functions(self, prefix=None):
598 c = self.cursor()
599 if prefix is None:
600 c.execute('select name, class, file, searchpattern from function')
601 else:
602 prefix = str(prefix)
603 c.execute(
604 'select name, class, file, searchpattern from function where name like (?)', (
605 prefix + '%',))
606 return [(name, pat, klassid, fileid) for name, klassid, fileid, pat in c]
607 #@+node:ekr.20110310091639.14265: *3* file_id
608 def file_id(self, fname):
609 if fname == '':
610 return 0
611 idd = self.filecache.get(fname)
612 if idd is None:
613 c = self.cursor()
614 c.execute('insert into file(path) values (?)', [fname])
615 idd = c.lastrowid
616 self.filecache[fname] = idd
617 self.fileids_scanned.add(idd)
618 else:
619 if idd in self.fileids_scanned:
620 return idd
621 # we are rescanning a file with old entries - nuke old entries
622 #print "rescan", fname
623 c = self.cursor()
624 c.execute("delete from function where file = (?)", (idd,))
625 #self.dbconn.commit()
626 self.fileids_scanned.add(idd)
627 return idd
628 #@+node:ekr.20110310091639.14266: *3* feed_function
629 def feed_function(self, func_name, class_name, file_name, aux):
630 """ insert one function
632 'aux' can be a search pattern (as with ctags), signature, or description
635 """
636 clid = self.class_id(class_name)
637 fid = self.file_id(file_name)
638 c = self.cursor()
639 c.execute(
640 'insert into function(class, name, searchpattern, file) values (?, ?, ?, ?)',
641 [clid, func_name, aux, fid])
642 #@+node:ekr.20110310091639.14267: *3* feed_scintilla
643 def feed_scintilla(self, apifile_obj):
644 """ handle scintilla api files
646 Syntax is like:
648 qt.QApplication.style?4() -> QStyle
649 """
650 for l in apifile_obj:
651 parts = l.split('?')
652 fullsym = parts[0].rsplit('.', 1)
653 klass, func = fullsym
654 if len(parts) == 2:
655 desc = parts[1]
656 else:
657 desc = ''
658 # now our class is like qt.QApplication. We do the dirty trick and
659 # remove all but actual class name
660 shortclass = klass.rsplit('.', 1)[-1]
661 #print func, klass, desc
662 self.feed_function(func.strip(), shortclass.strip(), '', desc.strip())
663 self.dbconn.commit()
664 #@+node:ekr.20110310091639.14268: *3* feed_ctags
665 def feed_ctags(self, tagsfile_obj):
666 for l in tagsfile_obj:
667 if l.startswith('!'):
668 continue
669 fields = l.split('\t')
670 m = fields[0]
671 fil = fields[1]
672 pat = fields[2]
673 # typ = fields[3]
674 klass = None
675 try:
676 ext = fields[4]
677 if ext and ext.startswith('class:'):
678 klass = ext.split(':', 1)[1].strip()
679 idd = self.class_id(klass)
680 #print "klass",klass, idd
681 except IndexError:
682 ext = None
683 # class id 0 = function
684 idd = 0
685 c = self.cursor()
686 #print fields
687 fid = self.file_id(fil)
688 c.execute(
689 'insert into function(class, name, searchpattern, file) values (?, ?, ?, ?)',
690 [idd, m, pat, fid])
691 self.dbconn.commit()
692 #c.commit()
693 #print fields
694 #@+node:ekr.20110310091639.14269: *3* add_source
695 def add_source(self, type, src):
696 c = self.cursor()
697 c.execute('insert into datasource(type, src) values (?,?)', (type, src))
698 self.dbconn.commit()
699 #@+node:ekr.20110310091639.14270: *3* sources
700 def sources(self):
701 c = self.cursor()
702 c.execute('select type, src from datasource')
703 return list(c)
704 #@+node:ekr.20110310091639.14271: *3* zap_symbols
705 def zap_symbols(self):
706 c = self.cursor()
707 tables = ['class', 'file', 'function']
708 for t in tables:
709 c.execute('delete from ' + t)
710 self.dbconn.commit()
711 #@+node:ekr.20110310091639.14272: *3* # high level commands
712 # high level commands
713 #@+node:ekr.20110310091639.14273: *3* parseall
714 def parseall(self):
715 sources = self.sources()
716 self.reset_caches()
717 self.zap_symbols()
718 tagdirs = [td for typ, td in sources if typ == 'tagdir']
719 self.parse(tagdirs)
720 self.dbconn.commit()
721 #@+node:ekr.20110310091639.14274: *3* parse
722 def parse(self, paths):
723 paths = set(os.path.abspath(p) for p in paths)
724 f = run_ctags(paths)
725 self.feed_ctags(f)
726 sources = self.sources()
727 for a in paths:
728 if ('tagdir', a) not in sources:
729 self.add_source('tagdir', a)
730 #@-others
731#@+node:ekr.20110310091639.14275: ** class ContextSniffer
732class ContextSniffer:
733 """ Class to analyze surrounding context and guess class
735 For simple dynamic code completion engines
737 """
738 #@+others
739 #@+node:ekr.20110310091639.14276: *3* __init__ (ContextSniffer)
740 def __init__(self):
741 # var name => list of classes
742 self.vars = {}
743 #@+node:ekr.20110310091639.14277: *3* declare
744 def declare(self, var, klass):
745 # print("declare",var,klass)
746 vars = self.vars.get(var, [])
747 if not vars:
748 self.vars[var] = vars
749 vars.append(klass)
750 #@+node:ekr.20110310091639.14278: *3* push_declarations
751 def push_declarations(self, body):
752 for l in body.splitlines():
753 l = l.lstrip()
754 if not l.startswith('#'):
755 continue
756 l = l.lstrip('#')
757 parts = l.strip(':')
758 if len(parts) != 2:
759 continue
760 self.declare(parts[0].strip(), parts[1].strip())
761 #@+node:ekr.20110310091639.14279: *3* set_small_context
762 def set_small_context(self, body):
763 """ Set immediate function """
764 self.push_declarations(body)
765 #@-others
766#@-others
767#@@language python
768#@@tabwidth -4
769#@@pagewidth 70
771if __name__ == "__main__":
772 main()
773#@-leo