Hide keyboard shortcuts

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 

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

350 

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. 

414 

415# Codewise 

416 

417def isBytes(s): 

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

419 return isinstance(s, bytes) 

420 

421def isCallable(obj): 

422 return hasattr(obj, '__call__') 

423 

424def isString(s): 

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

426 return isinstance(s, str) 

427 

428def isUnicode(s): 

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

430 return isinstance(s, str) 

431 

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 

477 

478def ue(s, encoding): 

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

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

481def main(): 

482 

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 

631 

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

633 

634 

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 

645 

646 Syntax is like: 

647 

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 

734 

735 For simple dynamic code completion engines 

736 

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 

770 

771if __name__ == "__main__": 

772 main() 

773#@-leo