Sublime Forum

ST3 style autocomplete in ST4

#16

Thanks for creating an example. I suspect it won’t be really efficient for bigger files as it reparses on each change. One might use TextChangeListener instead.

The bigger issue any other things supplying completions like Anaconda or new Sublime Text 4 index won’t be used here. Maybe using a separate key binding for those and Tab for fast completion like this would be enough though.

0 Likes

#17

I’m coming from CLion IDE and Tab Completion works just like this, it’s call “Hippie Word Completion” or “Cyclic Expand Word”, it also works similarly on Emacs, for more advance completion you press Ctrl+Space on CLion. I’m planning on using this plugin. Can you please give me an example on how to use “TextChangeListener” ? So I can incorporate it in my code. Thanks.

0 Likes

#18

For more advanced completion you can extend LSP package,
here’s an example


import weakref
from .core.protocol import Request, SymbolInformation, SymbolTag
from .core.registry import LspTextCommand
from .core.typing import Any, List, Tuple, Dict, Union
from .core.views import SYMBOL_KINDS
import os
import sublime


SUPPRESS_INPUT_SETTING_KEY = 'lsp_suppress_input'


def unpack_lsp_kind(kind: int) -> Tuple[int, str, str, str]:
    if 1 <= kind <= len(SYMBOL_KINDS):
        return SYMBOL_KINDS[kind - 1]
    return sublime.KIND_ID_AMBIGUOUS, "?", "???", "comment"


def format_symbol_kind(kind: int) -> str:
    if 1 <= kind <= len(SYMBOL_KINDS):
        return SYMBOL_KINDS[kind - 1][2]
    return str(kind)


def get_symbol_scope_from_lsp_kind(kind: int) -> str:
    if 1 <= kind <= len(SYMBOL_KINDS):
        return SYMBOL_KINDS[kind - 1][3]
    return 'comment'


def symbol_information_to_name(
    item: SymbolInformation,
    show_file_name: bool = True
) -> str:
    st_kind, st_icon, st_display_type, _ = unpack_lsp_kind(item['kind'])
    tags = item.get("tags") or []
    if SymbolTag.Deprecated in tags:
        st_display_type = "⚠ {} - Deprecated".format(st_display_type)
    container = item.get("containerName") or ""
    details = []  # List[str]
    if container:
        details.append(container)
    if show_file_name:
        file_name = os.path.basename(item['location']['uri'])
        details.append(file_name)
    return item["name"]



class LspWorkspaceSymbolsTwoCommand(LspTextCommand):

    capability = 'workspaceSymbolProvider'

    def run(self, edit: sublime.Edit, symbol_query_input: str) -> None:
        if symbol_query_input:
            session = self.best_session(self.capability)
            if session:
                params = {"query": symbol_query_input}
                request = Request("workspace/symbol", params, None, progress=True)
                self.weaksession = weakref.ref(session)
                session.send_request(request, lambda r: self._handle_response(
                    symbol_query_input, r), self._handle_error)

    def _open_file(self, symbols: List[SymbolInformation], index: int) -> None:
        if index != -1:
            session = self.weaksession()
            if session:
                session.open_location_async(symbols[index]['location'], sublime.ENCODED_POSITION)

    def _handle_response(self, query: str, response: Union[List[SymbolInformation], None]) -> None:
        if response:
            matches = response
            print(list(map(symbol_information_to_name, matches)))
            # window = self.view.window()
            # if window:
            #     window.show_quick_panel(
            #         list(map(symbol_information_to_quick_panel_item, matches)),
            #         lambda i: self._open_file(matches, i))
        else:
            sublime.message_dialog("No matches found for query string: '{}'".format(query))

    def _handle_error(self, error: Dict[str, Any]) -> None:
        reason = error.get("message", "none provided by server :(")
        msg = "command 'workspace/symbol' failed. Reason: {}".format(reason)
        sublime.error_message(msg)

run this in command palate to test it out

view.run_command('lsp_workspace_symbols_two', {"symbol_query_input": "Tex"})
1 Like

Single keypress tab completion not allowed anymore in sublime text 4
#19

Refined the initial example by LightsOut8008: https://github.com/Suor/sublime-hippie-autocomplete.

Uses fuzzy search now, protect from huge views. Many things to be desired still but usable.

P.S. Renamed the repo and updated link here.

2 Likes

#20

Nice,
Btw I have already knocked out couple of TODOs in my code

  1. For multiple cursors:
		for caret in self.view.sel():
			self.view.replace(edit, self.view.word(caret), last_choice)
  1. Matching first letter
if word not in matching and word != search_word_text and word[0] == search_word_text[0]:

// word[0] == search_word_text[0] // this matches first letter

search_word_parts = re.findall('([A-Z])?([^A-Z]*)', search_word_text)
search_word_parts = [item for t in search_word_parts for item in t if item]

// above code separates searched word into lower and uppercase
so if you want search for : MyAwesomeVariable -  you type MAV then tab 
  1. prefer words closer to cursor?
    This splits the list in two halves (one from cursor to beginning of file and second from cursor to end of the file) and joins them end to end, so words starting from above the cursor will be at beginning of the list and words below cursor would be in reverse at the end of the list. You have the preference to cycle from beginning or the end.
first_half  = view.substr(sublime.Region(0, view.sel()[0].begin()))
second_half = view.substr(sublime.Region(view.sel()[0].begin(), view.size()))
word_list = word_pattern.findall(second_half)
word_list.extend(word_pattern.findall(first_half))

I’ll also update previously pasted code above.
Thanks.

0 Likes

#21
  1. What if primers are different?
  2. Didn’t work for me. I don’t see how it should, say I am trying to expand wl into word_list:
    • regex finds single word part instead of 2 – w and l
    • even if regex did it properly keys are looked in the word in any place not at the word starts
    • doesn’t try to work for global word list
  3. I saw that, but once I use fuzzy matching it’s either it’s scoring or preserving order. I chose fuzzy scoring so far.
0 Likes

#22
  1. I’m not familiar with primers in your code, so can’t answer that.

  2. That works for Capitalized words camelCase PascalCase etc without much effort. if you want support lower_case, need to do little more work on ‘candidate word’ after matching first letter against searched word.

  3. That’s up to preference I guess.

0 Likes

#23

great, just starred it on github and added it to the Sublime Text trhough git with Sublime Merge.

Well it is your code. Feel free to open a github account and sync your lastest changes there.

Thanks for the plugin, guys!

0 Likes

auto complete problem
#24
search_word_parts = re.findall('([A-Z])?([^A-Z]*)', search_word_text)
search_word_parts = [item for t in search_word_parts for item in t if item]
# print(search_word_parts)

# [matching.append(s) for s in reversed(word_list) if s not in matching and search_word_text in s and s != search_word_text]
for word in reversed(word_list):
    found = False
    if word not in matching and word != search_word_text and word[0] == search_word_text[0]:
        if len(search_word_text) > 1 and '_' in word: \\ this if clause is for supporting 'snake_case' words
            for char in search_word_text:
                if char in word:
                    found = True
                else:
                    found = False
                    break
        else:
            for word_part in  search_word_parts:
                if word_part in word:
                    found = True
                else:
                    found = False
                    break
    if found:
        matching.append(word)

added if clause to support ‘snake_case’ words now - wl ->Tab should expand to word_list

0 Likes

#25

I guess we are trying to achieve different things. I want wl to reliably expand into word_list first and then to bowl and then to wall_2. Your code will order them the same as the list.

BTW, now words that go just before the cursor will be last in your candidates list. You might want to reverse those.

0 Likes

#26

Well my preference is to expands to words above and closest to my cursor first, I’m not sure how you have ordered your list, if you use previously mention method to order the list, you can have preference to either expand to words closest and above to the cursor or closest and below the cursor.

0 Likes

#27

I currently save words into set, do not preserve order. Order them by shortest match span.

0 Likes

#28

I have created a fork https://github.com/litezzzout/sublime-hippie-words,
-added cycle back ability.
-added ability to add candidate words from other files once finished cycling through current file.

2 Likes

#29

I added ability to prioritize matching first letters in combined_words
I personaly don’t care for it but, realized other people may find it useful since everyone doesn’t use camelCase or PascalCase. Later I may add setting to enable/disable it.

0 Likes

#30

I added expanding abbreviations support for snake_case, CameCase and mixedCase too. Adjusted how scoring works:

wl -> word_list, word_list_index, wall
wli -> word_list_index, word_list
wol -> word_list, wollie, world

Also added simple history of chosen completions they will be selected first.

BTW, there was a bug in key bindings - indent almost never worked. Fixed it here, you might want to do the same.

0 Likes

#31

I mean if words under cursors differ then it will replace all of them with a suggestion for the first one. So current behavior is incorrect. It is still useful though, so I went for the same implementation as yours for now, might fix it later.

0 Likes

#32

I personally didn’t face any problem indenting a single line rather I couldn’t indent multiple lines with selection, so I fixed that.

On SublimeText3 it inserts 4 spaces or [tab] instead of completion, I personally don’t find it more useful that what we already have, but of course we can change it later.

0 Likes

#33

Your { "key": "selection_empty", ... "match_all": true } approach works for the most part, but prevents Ctrl+D, Ctrl+D, …, Tab usage. I find a bug with mine as well though, investigating now.

P.S. Added { "key": "text", "operator": "not_regex_contains", "operand": "\\s", "match_all": true }, for now

0 Likes

#34

on ST3 ctrl+d ctrl+d … Tab deletes selected words, it does the same on our version.

0 Likes

#35

I strongly agree with Suors request.
I also was very disappointed after the update to version 4. I found that it

  • has new color theme
  • is slower
  • habitual and convenient Tab completion doesn’t work

Fixed this by sudo apt install sublime-text=3211

I really hope Sublime will grow like the best light and fast text editor, not one more fancy but slow IDE

2 Likes