Skip to content

Commit

Permalink
feat: Outline view #203
Browse files Browse the repository at this point in the history
Merge pull request from billyeatcookies/outline
  • Loading branch information
tomlin7 authored Jan 6, 2024
2 parents 13481c3 + c55f097 commit c86db9b
Show file tree
Hide file tree
Showing 21 changed files with 340 additions and 32 deletions.
8 changes: 7 additions & 1 deletion biscuit/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ def setup_references(self) -> None:
self.contentpane = self.root.baseframe.contentpane
self.sidebar = self.root.baseframe.sidebar
self.explorer = self.sidebar.explorer
self.outline = self.sidebar.outline
self.source_control = self.sidebar.source_control
self.extensionsGUI = self.sidebar.extensions
self.logger = self.panel.logger
Expand Down Expand Up @@ -247,10 +248,15 @@ def close_active_directory(self) -> None:

def close_active_editor(self) -> None:
self.editorsmanager.close_active_editor()

def goto_location_in_active_editor(self, position: int) -> None:
if editor := self.editorsmanager.active_editor:
if editor.content and editor.content.editable:
editor.content.goto(position)

def goto_location(self, path: str, position: int) -> None:
if self.editorsmanager.is_open(path):
self.editorsmanager.set_active_editor(path).content.goto(position)
self.editorsmanager.set_active_editor_by_path(path).content.goto(position)
return

editor = self.open_editor(path, exists=True)
Expand Down
15 changes: 9 additions & 6 deletions biscuit/core/components/editors/texteditor/text.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import codecs
import os
import queue
import re
import threading
Expand All @@ -26,6 +27,7 @@ def __init__(self, master: TextEditor, path: str=None, exists: bool=True, minima
super().__init__(master, *args, **kwargs)
self.master = master
self.path = path
self.filename = os.path.basename(path) if path else None
self.encoding = 'utf-8'
self.eol = "CRLF"
self.exists = exists
Expand Down Expand Up @@ -250,6 +252,7 @@ def refresh(self):

self.highlight_current_line()
self.highlight_current_word()
self.base.languageservermanager.request_outline(self)


# TODO send only portions of text on change to the LSPServer
Expand Down Expand Up @@ -372,11 +375,11 @@ def request_autocomplete(self, _):
def lsp_show_autocomplete(self, response: Completions) -> None:
self.autocomplete.lsp_update_completions(self, response.completions)

def lsp_diagnostics(self, response: Underlines) -> None:
print("LSP <<< ", response)
for i in response.underline_list:
# self.tag_add("error", f"{i.start[0]}.{i.start[1]}", f"{i.end[0]}.{i.end[1]}")
print(i.start, i.color, i.tooltip_text)
def lsp_diagnostics(self, response: Underlines) -> None: ...
# print("LSP <<< ", response)
# for i in response.underline_list:
# # self.tag_add("error", f"{i.start[0]}.{i.start[1]}", f"{i.end[0]}.{i.end[1]}")
# print(i.start, i.color, i.tooltip_text)

def lsp_goto_definition(self, response: Jump) -> None:
if not response.locations:
Expand Down Expand Up @@ -625,7 +628,7 @@ def event_destroy(self, _):

def event_unmapped(self, _):
self.hide_autocomplete()
self.base.languageservermanager.tab_closed(self)
self.base.outline.tree.update_symbols()

def event_copy(self, *_):
self.event_generate("<<Copy>>")
Expand Down
2 changes: 1 addition & 1 deletion biscuit/core/components/floating/autocomplete/kind.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from biscuit.core.components.utils.icon import Icon

from ...lsp.kinds import kinds
from .kinds import kinds

if typing.TYPE_CHECKING:
from .item import CompletionItem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
("symbol-text", None), # text
("symbol-method", "#AB7CCF"), # method
("symbol-function", "#AB7CCF"), # function
("symbol-constructor", "#AB7CCF"), # constructor
("symbol-constructor", "#6eb1ee"), # constructor
("symbol-field", "#6eb1ee"), # field
("symbol-variable", "#6eb1ee"), # variable
("symbol-class", "#de9327"), # class
Expand Down
11 changes: 8 additions & 3 deletions biscuit/core/components/floating/palette/actionset.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,15 @@ def update(self, items) -> None:
"Clear and update the items in the actionset."
self.clear()
self += items

def add_action(self, command: str, callback: Callable) -> None:
"Add an item to the actionset."
self.append((command, callback))

def add_pinned_actions(self, command: str, callback: Callable) -> None:
"Add an item to the pinned actionset."
self.pinned.append((command, callback))

def get_pinned(self, term) -> list:
"Returns the pinned items with the term formatted."
if not self.pinned:
return []

return [[item[0].format(term)] + item[1:] for item in self.pinned]
6 changes: 6 additions & 0 deletions biscuit/core/components/lsp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def tab_opened(self, tab: Text) -> None:
self.latest = self.request_client_instance(tab)
if self.latest:
self.latest.open_tab(tab)
self.latest.request_outline(tab)
return self.latest is not None

def request_removal(self, tab: Text) -> None:
Expand All @@ -53,6 +54,11 @@ def request_hover(self, tab: Text) -> str | None:
if tab in instance.tabs_opened:
instance.request_hover(tab)

def request_outline(self, tab: Text) -> None:
for instance in list(self.existing.values()):
if tab in instance.tabs_opened:
instance.request_outline(tab)

def content_changed(self, tab: Text) -> None:
for instance in list(self.existing.values()):
if tab in instance.tabs_opened:
Expand Down
14 changes: 12 additions & 2 deletions biscuit/core/components/lsp/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def __init__(self, master: LanguageServerManager, tab: Text, root_dir: str) -> N
self._autocomplete_req: dict[int, tuple[Text, CompletionRequest]] = {}
self._hover_requests: dict[int, tuple[Text, str]] = {}
self._gotodef_requests: dict[int, tuple[Text, str]] = {}
self._outline_requests: dict[int, Text] = {}

def run_loop(self) -> None:
if self.run():
Expand All @@ -61,7 +62,7 @@ def run(self):
for lsp_event in self.client.recv(r):
self.handler.process(lsp_event)
except Exception as e:
pass
print(e)

return True

Expand Down Expand Up @@ -96,7 +97,7 @@ def delayed_removal(self=self):

if self.master.kill_thread:
self.base.after_cancel(self.master.kill_thread)
self.master.kill_thread = self.base.after(5000, delayed_removal)
self.master.kill_thread = self.base.after(50000, delayed_removal)

def request_completions(self, tab: Text) -> None:
if tab.path is None or self.client.state != lsp.ClientState.NORMAL:
Expand Down Expand Up @@ -138,6 +139,15 @@ def request_go_to_definition(self, tab: Text) -> None:
)
)
self._gotodef_requests[request_id] = (tab, tab.get_mouse_pos())

def request_outline(self, tab: Text) -> None:
if tab.path is None or self.client.state != lsp.ClientState.NORMAL:
return

request_id = self.client.documentSymbol(
lsp.TextDocumentIdentifier(uri=Path(tab.path).as_uri()),
)
self._outline_requests[request_id] = tab

def send_change_events(self, tab: Text) -> None:
if self.client.state != lsp.ClientState.NORMAL:
Expand Down
5 changes: 4 additions & 1 deletion biscuit/core/components/lsp/data.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from __future__ import annotations

import dataclasses
import json
from typing import List, Optional, Union

import sansio_lsp_client as lsp

# Requests

@dataclasses.dataclass
Expand Down
9 changes: 9 additions & 0 deletions biscuit/core/components/lsp/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def process(self, e: lsp.Event) -> None:
self.base.logger.info("Capabilities " + pprint.pformat(e.capabilities))
for tab in self.master.tabs_opened:
self.master.open_tab(tab)
self.master.request_outline(tab)

self.base.statusbar.process_indicator.hide()
return
Expand Down Expand Up @@ -127,3 +128,11 @@ def process(self, e: lsp.Event) -> None:
requesting_tab, location = self.master._hover_requests.pop(e.message_id)
requesting_tab.lsp_hover(HoverResponse(location, *hover_filter(e.contents)))
return

if isinstance(e, lsp.MDocumentSymbols):
tab = self.master._outline_requests.pop(e.message_id)
if tab not in self.master.tabs_opened:
return

self.base.outline.update_symbols(tab, e.result if e.result and isinstance(e.result[0], lsp.DocumentSymbol) else to_document_symbol(e.result))
return
45 changes: 44 additions & 1 deletion biscuit/core/components/lsp/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from urllib.request import url2pathname

import sansio_lsp_client as lsp
from tomlkit import key


def get_completion_item_doc(item: lsp.CompletionItem) -> str:
Expand Down Expand Up @@ -34,7 +35,7 @@ def jump_paths_and_ranges(locations: list[lsp.Location] | lsp.Location) -> Itera

def hover_filter(content: lsp.MarkupContent | str) -> list[Optional[list[str, str]], str]:
if not isinstance(content, lsp.MarkupContent):
return
return None, None

value = content.value.strip()
if value.startswith("```"):
Expand All @@ -52,3 +53,45 @@ def encode_position(pos: str | list[int]) -> lsp.Position:

def decode_position(pos: lsp.Position) -> str:
return f"{pos.line + 1}.{pos.character}"

def contains_range(range: lsp.Range, pos: lsp.Range) -> bool:
if pos.start.line < range.start.line or pos.end.line > range.end.line:
return False
if pos.start.line == range.start.line and pos.start.character < range.start.character:
return False
if pos.end.line == range.end.line and pos.end.character > range.end.character:
return False
return True

def equals_range(a: lsp.Range, b: lsp.Range) -> bool:
if not (a or b):
return True

return a and b and a.start.line == b.start.line and a.start.character == b.start.character and a.end.line == b.end.line and a.end.character == b.end.character

def to_document_symbol(infos: list[lsp.SymbolInformation]) -> list[lsp.DocumentSymbol]:
if not infos:
return []

infos.sort(key=lambda x: ((x.location.range.start.line, x.location.range.start.character), (-x.location.range.end.line, -x.location.range.end.character)))
res: list[lsp.DocumentSymbol] = []
parents: list[lsp.DocumentSymbol] = []

for info in infos:
element = lsp.DocumentSymbol(name=info.name or '!!MISSING: name!!', kind=info.kind, children=[],
range=info.location.range, selectionRange=info.location.range)

while True:
if not parents:
parents.append(element)
res.append(element)
break
parent = parents[-1]
if contains_range(parent.range, element.range) and not equals_range(parent.range, element.range):
# TODO avoid adding the same named element twice to same parent
parent.children.append(element)
parents.append(element)
break
parents.pop()

return res
12 changes: 6 additions & 6 deletions biscuit/core/components/views/panel/terminal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(self, master, *args, **kwargs) -> None:
self.tabs = Tabs(self)
self.tabs.grid(row=0, column=1, padx=(1, 0), sticky=tk.NS)

self.terminals = []
self.active_terminals = []

self.default_terminal = Default(self, cwd=self.base.active_directory or get_home_directory())
self.add_terminal(self.default_terminal)
Expand All @@ -52,7 +52,7 @@ def add_terminals(self, terminals) -> None:

def add_terminal(self, terminal) -> None:
"Appends a terminal to list. Create a tab."
self.terminals.append(terminal)
self.active_terminals.append(terminal)
self.tabs.add_tab(terminal)

def open_shell(self, shell) -> None:
Expand All @@ -63,16 +63,16 @@ def open_terminal(self, path=None) -> None:

def delete_all_terminals(self) -> None:
"Permanently delete all terminals."
for terminal in self.terminals:
for terminal in self.active_terminals:
terminal.destroy()

self.tabs.clear_all_tabs()
self.terminals.clear()
self.active_terminals.clear()

def delete_terminal(self, terminal) -> None:
"Permanently delete a terminal."
terminal.destroy()
self.terminals.remove(terminal)
self.active_terminals.remove(terminal)

def delete_active_terminal(self) -> None:
"Closes the active tab"
Expand Down Expand Up @@ -116,5 +116,5 @@ def active_terminal(self):
return self.tabs.active_tab.terminal

def refresh(self) -> None:
if not self.terminals:
if not self.active_terminals:
self.master.toggle_panel()
2 changes: 1 addition & 1 deletion biscuit/core/components/views/panel/terminal/shells/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class CommandPrompt(Terminal):
"""
shell = "cmd"
name = "Command Prompt"
name = "cmd"
icon = "cmd"
def __init__(self, master, *args, **kwargs) -> None:
super().__init__(master, *args, **kwargs)
Expand Down
5 changes: 3 additions & 2 deletions biscuit/core/components/views/sidebar/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .sidebarview import SidebarView
from .explorer import Explorer
from .extensions import Extensions
from .outline import Outline
from .search import Search
from .sidebarview import SidebarView
from .sourcecontrol import SourceControl
from .extensions import Extensions
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,6 @@ def run_create_sub_root(self, path: str, parent='') -> None:
self.update_treeview(path, parent)
self.loading = False

def get_actionset(self) -> ActionSet:
return self.actionset

def get_all_files(self) -> list:
files = []
for item in self.tree.get_children():
Expand Down
28 changes: 28 additions & 0 deletions biscuit/core/components/views/sidebar/outline/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from __future__ import annotations

import typing

import sansio_lsp_client as lsp

from biscuit.core.components.floating.palette import ActionSet

from ..sidebarview import SidebarView
from .outlinetree import OutlineTree

if typing.TYPE_CHECKING:
from biscuit.core.components.editors.texteditor import Text

class Outline(SidebarView):
def __init__(self, master, *args, **kwargs) -> None:
self.__buttons__ = [('refresh',), ('collapse-all',), ('ellipsis',),]
super().__init__(master, *args, **kwargs)
self.__icon__ = 'symbol-class'

self.tree = OutlineTree(self)
self.add_widget(self.tree)

def update_symbols(self, tab: Text, response: list[lsp.DocumentSymbol]) -> str:
return self.tree.update_symbols(tab, response)

def get_actionset(self) -> ActionSet:
...
28 changes: 28 additions & 0 deletions biscuit/core/components/views/sidebar/outline/kinds.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
kinds = (
("symbol-file", None),
("symbol-module", None),
("symbol-namespace", None),
("symbol-package", None),
("symbol-class", "#de9327"),
("symbol-method", "#AB7CCF"),
("symbol-property", "#6eb1ee"),
("symbol-field", "#6eb1ee"),
("symbol-constructor", "#6eb1ee"),
("symbol-enum", None),
("symbol-interface", "#de9327"),
("symbol-function", "#AB7CCF"),
("symbol-variable", "#6eb1ee"),
("symbol-constant", None),
("symbol-string", None),
("symbol-number", None),
("symbol-boolean", "#de9327"),
("symbol-array", None),
("symbol-object", None),
("symbol-key", None),
("symbol-null", None),
("symbol-enum-member", None),
("symbol-struct", None),
("symbol-event", "#ffd700"),
("symbol-operator", None),
("symbol-type-parameter", "#ffd700")
)
Loading

0 comments on commit c86db9b

Please sign in to comment.