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

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

434 statements  

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 

8 

9Why this module 

10=============== 

11 

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. 

18 

19Creating ctags data 

20=================== 

21 

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. 

25 

26 

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. 

31 

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:: 

35 

36 --exclude=*.html 

37 --exclude=*.css 

38 

393. Create the ctags data in ~/.codewise.db using this module. Execute the 

40 following from a console window:: 

41 

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> 

49 

50**Note**: On Windows, use a batch file, say codewise.bat, to execute the 

51above code. codewise.bat contains:: 

52 

53 python <path to leo>\leo\external\codewise.py %* 

54 

55Using the autocompleter 

56======================= 

57 

58After restarting Leo, type, for example, in the body pane:: 

59 

60 c.op<ctrl-space> 

61 

62that is, use use the autocomplete-force command, 

63to find all the c. methods starting with 'op' etc. 

64 

65Theory of operation 

66=================== 

67 

68- ~/.codewise.db is an sqlite database with following tables: 

69 

70CLASS maps class id's to names. 

71 

72FILE maps file id's to file names 

73 

74DATASOURCE contains places where data has been parsed from, to enable reparse 

75 

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. 

80 

81You can browse the data by installing sqlitebrovser and doing 'sqlitebrowser 

82~/codewise.db' 

83 

84If you know the class name you want to find the methods for, 

85CodeWise.get_members with a list of classes to match. 

86 

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. 

89 

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 

100 

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) 

108 

109codewise init 

110 Create/recreate the global database 

111 

112codewise parse /my/project /other/project 

113 Parse specified directories (with recursion) and add results to db 

114 

115codewise m 

116 List all classes 

117 

118codewise m MyClass 

119 Show all methods in MyClass 

120 

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 

124 

125codewise parseall 

126 Clear database, reparse all paths previously added by 'codewise parse' 

127 

128codewise sciapi pyqt.api 

129 Parse an api file (as supported by scintilla, eric4...) 

130 

131Commands you don't probably need: 

132 

133codewise tags TAGS 

134 Dump already-created tagfile TAGS to database 

135 

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); 

146 

147CREATE INDEX idx_class_name ON class(name ASC); 

148CREATE INDEX idx_function_class ON function(class ASC); 

149 

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): 

202 

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. 

220 

221 If the excludeCaller keyword is True (the default), callers is not on the list. 

222 

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 

315 

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. 

349 

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. 

413 

414# Codewise 

415 

416def isBytes(s): 

417 '''Return True if s is Python3k bytes type.''' 

418 return isinstance(s, bytes) 

419 

420def isCallable(obj): 

421 return hasattr(obj, '__call__') 

422 

423def isString(s): 

424 '''Return True if s is any string, but not bytes.''' 

425 return isinstance(s, str) 

426 

427def isUnicode(s): 

428 '''Return True if s is a unicode string.''' 

429 return isinstance(s, str) 

430 

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 

476 

477def ue(s, encoding): 

478 return s if isUnicode(s) else str(s, encoding) 

479#@+node:ekr.20110310091639.14290: *3* main 

480def main(): 

481 

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 

630 

631 'aux' can be a search pattern (as with ctags), signature, or description 

632 

633 

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 

644 

645 Syntax is like: 

646 

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 

733 

734 For simple dynamic code completion engines 

735 

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 

769 

770if __name__ == "__main__": 

771 main() 

772#@-leo