Advertisement
Guest User

__init__.py

a guest
Jul 29th, 2024
61
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.22 KB | None | 0 0
  1. import os
  2. import re
  3. from cudatext import *
  4. import cudax_lib as appx
  5.  
  6. FN_CONFIG = os.path.join(app_path(APP_DIR_SETTINGS), 'plugins.ini')
  7. SECTION = 'complete_from_text'
  8. NONWORDS_DEFAULT = '''-+*=/\()[]{}<>"'.,:;~?!@#$%^&|`…''' # without space/tab
  9. nonwords = ''
  10.  
  11. def bool_to_str(v): return '1' if v else '0'
  12. def str_to_bool(s): return s=='1'
  13.  
  14. # '-' here is none-lexer
  15. option_lexers = ini_read(FN_CONFIG, SECTION, 'lexers', '-,ini files ^,markdown,restructuredtext,properties')
  16. option_min_len = int(ini_read(FN_CONFIG, SECTION, 'min_len', '3'))
  17. option_case_sens = str_to_bool(ini_read(FN_CONFIG, SECTION, 'case_sens', '1'))
  18. #option_no_cmt = str_to_bool(ini_read(FN_CONFIG, SECTION, 'no_comments', '1'))
  19. #option_no_str = str_to_bool(ini_read(FN_CONFIG, SECTION, 'no_strings', '0'))
  20. option_what_editors = int(ini_read(FN_CONFIG, SECTION, 'what_editors', '0'))
  21. option_max_lines = int(ini_read(FN_CONFIG, SECTION, 'max_lines', '10000'))
  22. option_use_acp = str_to_bool(ini_read(FN_CONFIG, SECTION, 'use_acp', '1'))
  23. option_show_acp_first = str_to_bool(ini_read(FN_CONFIG, SECTION, 'show_acp_first', '0'))
  24. option_case_split = str_to_bool(ini_read(FN_CONFIG, SECTION, 'case_split', '0'))
  25. option_underscore_split = str_to_bool(ini_read(FN_CONFIG, SECTION, 'underscore_split', '0'))
  26. option_fuzzy = str_to_bool(ini_read(FN_CONFIG, SECTION, 'fuzzy_search', '1'))
  27.  
  28.  
  29. def get_editors(ed, lexer):
  30.  
  31.     if option_what_editors==0:
  32.         return [ed]
  33.     elif option_what_editors==1:
  34.         return [Editor(h) for h in ed_handles()]
  35.     elif option_what_editors==2:
  36.         res = []
  37.         for h in ed_handles():
  38.             e = Editor(h)
  39.             if e.get_prop(PROP_LEXER_FILE)==lexer:
  40.                 res += [e]
  41.         return res
  42.  
  43.  
  44. def is_lexer_allowed(lex):
  45.  
  46.     return (',*,' in ','+option_lexers+',') or (','+(lex or '-').lower()+',' in ','+option_lexers.lower()+',')
  47.  
  48.  
  49. def isword(s):
  50.  
  51.     return s not in ' \t'+nonwords
  52.  
  53.  
  54. def is_plain_match(stext, sfind):
  55.  
  56.     if option_case_sens:
  57.         return stext.startswith(sfind)
  58.     else:
  59.         return stext.upper().startswith(sfind.upper())
  60.  
  61.  
  62. def is_fuzzy_match(stext, sfind):
  63.     '''Ported from CudaText's Pascal code'''
  64.     stext = stext.upper()
  65.     sfind = sfind.upper()
  66.     n = -1
  67.     for ch in sfind:
  68.         n = stext.find(ch, n+1)
  69.         if n<0:
  70.             return False
  71.     return True
  72.  
  73.  
  74. def is_text_with_begin(s, begin):
  75.  
  76.     if option_fuzzy:
  77.         return is_fuzzy_match(s, begin)
  78.  
  79.     if option_case_split or option_underscore_split:
  80.         if option_case_split  and  s[0] == begin[0]:
  81.             searchwords = re.findall('.[^A-Z]*', begin)
  82.             wordwords = re.findall('.[^A-Z]*', s)
  83.  
  84.             swl = len(searchwords)
  85.             wwl = len(wordwords)
  86.             if swl <= wwl:
  87.                 for i in range(swl):
  88.                     if not wordwords[i].startswith(searchwords[i]):
  89.                         break
  90.                 else:
  91.                     return True
  92.  
  93.         if option_underscore_split:
  94.             ss = s.strip('_') # ignore starting,ending underscores
  95.             if '_' in ss:
  96.  
  97.                 if not option_case_sens:
  98.                     begin = begin.lower()
  99.                     ss = ss.lower()
  100.  
  101.                 wordwords = re.findall('[^_]+', ss) # PROP_LEX => [PROP, LEX]
  102.                 wwl = len(wordwords)
  103.                 bl = len(begin)
  104.  
  105.                 if bl <= wwl: # most common case, first chars of each word: PLF|PL|P => PROP_LEXER_FILE
  106.                     for i in range(bl):
  107.                         if begin[i] != wordwords[i][0]:
  108.                             break
  109.                     else:
  110.                         return True
  111.  
  112.                 if spl_match(begin, wordwords):
  113.                     return True
  114.  
  115.     if option_case_sens:
  116.         return s.startswith(begin)
  117.     else:
  118.         return s.upper().startswith(begin.upper())
  119.  
  120.  
  121. def spl_match(begin, words):
  122.     '''returns True if 'begin' fits consecutively into 'words' (prefixes of 'words')'''
  123.  
  124.     word = words[0]
  125.     common_len = 0
  126.     for i in range(min(len(begin), len(word))):
  127.         if begin[i] == word[i]:
  128.             common_len = i+1
  129.         else:
  130.             break
  131.  
  132.     if common_len > 0:
  133.         if len(begin) == common_len: # last match success
  134.             return True
  135.         elif len(words) == 1: # last word - should be full prefix
  136.             return False
  137.  
  138.         for i in range(common_len): # i: 0 and 1 for common_len=2 ->
  139.             # ... on calling spl_match() 'begin' will always be non-empty - less than full match
  140.             res = spl_match(begin[common_len-i:], words[1:])
  141.             if res:
  142.                 return True
  143.  
  144.     return False
  145.  
  146.  
  147. def escape_regex(s):
  148.  
  149.     res = ''
  150.     for ch in s:
  151.         if ch in '$^#-+|.?*()[]{}':
  152.             res += '\\'
  153.         res += ch
  154.     return res
  155.  
  156. def get_regex(begin, nonwords):
  157.  
  158.     w_content = r'\w'
  159.     for ch in NONWORDS_DEFAULT:
  160.         if ch not in nonwords:
  161.             w_content += escape_regex(ch)
  162.  
  163.     repeats = max(1, option_min_len-len(begin))
  164.     regex = r'\$?\b' + escape_regex(begin) + '[' + w_content + ']{' + str(repeats) + ',}'
  165.  
  166.     #print('begin:', repr(begin))
  167.     #print('regex:', repr(regex))
  168.     return regex
  169.  
  170. def get_words_list(ed, regex):
  171.  
  172.     if ed.get_line_count() > option_max_lines:
  173.         return []
  174.  
  175.     '''
  176.    if option_no_cmt and option_no_str:
  177.        ops = 'T6'
  178.    elif option_no_cmt:
  179.        ops = 'T4'
  180.    elif option_no_str:
  181.        ops = 'T5'
  182.    else:
  183.        ops = ''
  184.  
  185.    res = ed.action(EDACTION_FIND_ALL, regex, 'r'+ops) or []
  186.    l = [ed.get_text_substr(*r) for r in res]
  187.    # slower than via python regex, 3 times
  188.    '''
  189.  
  190.     flags = re.I if not option_case_sens else 0
  191.     l = re.findall(regex, ed.get_text_all(), flags)
  192.     l = list(set(l))
  193.  
  194.     return l
  195.  
  196.  
  197. def get_word(ed, x, y):
  198.  
  199.     if not 0<=y<ed.get_line_count():
  200.         return
  201.     s = ed.get_text_line(y)
  202.     if not 0<x<=len(s):
  203.         return
  204.  
  205.     x0 = x
  206.     while (x0>0) and isword(s[x0-1]):
  207.         x0-=1
  208.     text1 = s[x0:x]
  209.  
  210.     x0 = x
  211.     while (x0<len(s)) and isword(s[x0]):
  212.         x0+=1
  213.     text2 = s[x:x0]
  214.  
  215.     return (text1, text2)
  216.  
  217.  
  218. def _read_acp_file(sfile):
  219.  
  220.     with open(sfile, 'r', encoding='utf-8', errors='ignore') as f:
  221.         lines = f.readlines()
  222.     return lines
  223.    
  224. def get_acp_words(ed, word1, word2):
  225.  
  226.     lex = ed.get_prop(PROP_LEXER_CARET, '')
  227.     sfile = os.path.join(app_path(APP_DIR_DATA), 'autocomplete', lex+'.acp')
  228.     if os.path.isfile(sfile):
  229.         acp_lines = _read_acp_file(sfile)
  230.     else:
  231.         sfile = os.path.join(app_path(APP_DIR_DATA), 'autocomplete', lex+'_.acp')
  232.         if os.path.isfile(sfile):
  233.             acp_lines = _read_acp_file(sfile)
  234.         else:
  235.             return [], set()
  236.  
  237.     target_word = word1+word2
  238.  
  239.     if len(acp_lines) > 0  and  acp_lines[0].startswith('#chars'):
  240.         del acp_lines[0]
  241.  
  242.     acp_words_plain = []
  243.     acp_words_fuzzy = []
  244.     words = set()
  245.     fstr = '{}|{}|{}'+chr(9)+'{}'
  246.     for line in acp_lines:
  247.         line = line.rstrip()
  248.         if not line:
  249.             continue
  250.  
  251.         # using '#chars'
  252.         #m = re.match('^([^\s]+)\s([\w'+ word_chars +']+)([^|]*)\|?(.*)?$', line) # ^Prefix Word Args Descr?
  253.         m = re.match('^([^\s]+)\s([^(| ]+)([^|]*)\|?(.*)?$', line) # ^Prefix Word Args Descr?
  254.         if not m:
  255.             continue
  256.  
  257.         # avoid 'm[index]' to support old Python 3.4
  258.         pre, word, args, descr = m.group(1), m.group(2).rstrip(), m.group(3), m.group(4) or ''
  259.  
  260.         if len(word) < option_min_len:
  261.             continue
  262.         if is_text_with_begin(word, word1)  and  word != word1  and  word != target_word:
  263.             word = word.replace('%20', ' ')
  264.             s = fstr.format(pre, word, args, descr)
  265.             if is_plain_match(word, word1):
  266.                 acp_words_plain.append(s)
  267.             else:
  268.                 acp_words_fuzzy.append(s)
  269.             words.add(word)
  270.  
  271.     acp_words = acp_words_plain + acp_words_fuzzy
  272.     return acp_words, words
  273.  
  274.  
  275. def get_completions(ed_self, x0, y0, with_acp, ignore_lexer=False):
  276.  
  277.     lex = ed_self.get_prop(PROP_LEXER_FILE, '')
  278.     if lex is None: return
  279.     if not ignore_lexer and not is_lexer_allowed(lex): return
  280.  
  281.     global nonwords
  282.     nonwords = appx.get_opt(
  283.         'nonword_chars',
  284.         NONWORDS_DEFAULT,
  285.         appx.CONFIG_LEV_ALL,
  286.         ed_self,
  287.         lex)
  288.  
  289.     word = get_word(ed_self, x0, y0)
  290.  
  291.     if not word: return
  292.     word1, word2 = word
  293.     if not word1: return # to fix https://github.com/Alexey-T/CudaText/issues/3175
  294.     if word1[0].isdigit(): return
  295.  
  296.     # filter by word1[0], not by entire word1, because fuzzy must be supported
  297.     regex = get_regex(word1[0], nonwords)
  298.  
  299.     words_by_tabs = []
  300.     tab_titles = []
  301.     for e in get_editors(ed_self, lex):
  302.         words_by_tabs.append(get_words_list(e, regex))
  303.         title = e.get_prop(PROP_TAB_TITLE).replace('|', '/')
  304.         tab_titles.append(title)
  305.     #print('words_by_tabs:', words_by_tabs)
  306.  
  307.     words = []
  308.     for w in words_by_tabs:
  309.         words += w
  310.  
  311.     def search_tab(w):
  312.         if option_what_editors in [1, 2]:
  313.             tabs_found = []
  314.             for i, words_by_tabs_ in enumerate(words_by_tabs):
  315.                 if w in words_by_tabs_:
  316.                     tabs_found.append(i)
  317.             if tabs_found:
  318.                 return '|' + '; '.join([tab_titles[tf] for tf in tabs_found])
  319.             return ''
  320.         else:
  321.             return ''
  322.  
  323.     #exclude numbers
  324.     #words = [w for w in words if not w.isdigit()]
  325.  
  326.     if words:
  327.         words = sorted(list(set(words)))
  328.  
  329.     acp_words, acp_set = get_acp_words(ed_self, word1, word2) if with_acp else ([], set())
  330.  
  331.     if not words and not acp_words:
  332.         return
  333.  
  334.     def get_prefix(w):
  335.         return 'var' if w.startswith('$') else 'text'
  336.  
  337.     word1and2 = word1+word2
  338.     words = [w for w in words
  339.              if w not in acp_set # do not repeat words from .acp
  340.              and w!=word1
  341.              and w!=word1and2
  342.              ]
  343.     words = [w for w in words
  344.              if is_text_with_begin(w, word1)
  345.              ]
  346.     words_plain = [w for w in words
  347.              if is_plain_match(w, word1)
  348.              ]
  349.     words_fuzzy = [w for w in words
  350.              if not is_plain_match(w, word1)
  351.              ]
  352.     words = words_plain + words_fuzzy
  353.  
  354.     words_decorated = [get_prefix(w)+'|'+w+search_tab(w) for w in words]
  355.     return (words_decorated, acp_words, word1, word2)
  356.  
  357.  
  358. class Command:
  359.    
  360.     def on_complete(self, ed_self):
  361.  
  362.         carets = ed_self.get_carets()
  363.         if len(carets)!=1: return # don't allow multi-carets
  364.         x0, y0, x1, y1 = carets[0]
  365.         if y1>=0: return # don't allow selection
  366.        
  367.         res = get_completions(ed_self, x0, y0, option_use_acp)
  368.         if res is None: return
  369.         words, acp_words, word1, word2 = res
  370.         word_list = acp_words+words if option_show_acp_first else words+acp_words
  371.         ed_self.complete('\n'.join(word_list), len(word1), len(word2))
  372.         return True
  373.  
  374.     def config(self):
  375.  
  376.         ini_write(FN_CONFIG, SECTION, 'lexers', option_lexers)
  377.         ini_write(FN_CONFIG, SECTION, 'min_len', str(option_min_len))
  378.         ini_write(FN_CONFIG, SECTION, 'case_sens', bool_to_str(option_case_sens))
  379.         #ini_write(FN_CONFIG, SECTION, 'no_comments', bool_to_str(option_no_cmt))
  380.         #ini_write(FN_CONFIG, SECTION, 'no_strings', bool_to_str(option_no_str))
  381.         ini_write(FN_CONFIG, SECTION, 'what_editors', str(option_what_editors))
  382.         ini_write(FN_CONFIG, SECTION, 'max_lines', str(option_max_lines))
  383.         ini_write(FN_CONFIG, SECTION, 'use_acp', bool_to_str(option_use_acp))
  384.         ini_write(FN_CONFIG, SECTION, 'show_acp_first', bool_to_str(option_show_acp_first))
  385.         ini_write(FN_CONFIG, SECTION, 'fuzzy_search', bool_to_str(option_fuzzy))
  386.         ini_write(FN_CONFIG, SECTION, 'case_split', bool_to_str(option_case_split))
  387.         ini_write(FN_CONFIG, SECTION, 'underscore_split', bool_to_str(option_underscore_split))
  388.         file_open(FN_CONFIG)
  389.  
  390.         lines = [ed.get_text_line(i) for i in range(ed.get_line_count())]
  391.         try:
  392.             index = lines.index('['+SECTION+']')
  393.             ed.set_caret(0, index)
  394.         except:
  395.             pass
  396.  
  397.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement