Skip to content

Instantly share code, notes, and snippets.

@Ekopalypse
Created November 24, 2023 16:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Ekopalypse/862c1e44b39f564308500f627eab02ef to your computer and use it in GitHub Desktop.
Save Ekopalypse/862c1e44b39f564308500f627eab02ef to your computer and use it in GitHub Desktop.
Using treesitter with Npp and PythonScript plugin
from Npp import notepad, editor
import ctypes
from ctypes import wintypes
from WinDialog import (
Dialog, WS, DS,
ListBox, TextBox
)
from WinDialog.controls.textbox import ES
from WinDialog.controls.listbox import LBS
from WinDialog.win_helper import GetWindowRect
from tree_sitter_languages import get_language, get_parser
class QueryLanguage(Dialog):
def __init__(self, title=''):
super().__init__(title)
self.size = (240, 94)
self.style = DS.SETFONT | WS.POPUP
self.pointsize = 11
self.typeface = 'Segoe UI'
self.textbox = TextBox('', (240, 11), (0, 0))
self.textbox.style = ES.LEFT | WS.CHILD | WS.VISIBLE | WS.BORDER | WS.TABSTOP
self.textbox.onChange = self.on_change
self.listbox = ListBox('', (240, 80), (0, 11))
self.listbox.style = WS.VISIBLE | WS.TABSTOP |LBS.NOINTEGRALHEIGHT
rect = self.get_scintilla_rect()
self.position = ((((rect.right-rect.left) // 2) - self.size[0]) //2, 0) # top centered
# self.center = True
self.queries = {
'string_select': lambda: self.select_current_string(),
'string_copy': lambda: self.select_current_string(True),
'function_select': lambda: self.select_current_function(),
'function_copy': lambda: self.select_current_function(True),
'add_docstring': self.add_docstring_to_current_function,
}
self.filtered_queries = list(self.queries.keys())
language__ = notepad.getLanguageName(notepad.getLangType()).lower()
self.language = get_language(language__)
self.tree = get_parser(language__).parse(editor.getText().encode())
self.show()
def get_scintilla_rect(self):
rect = wintypes.RECT()
GetWindowRect(editor.hwnd, ctypes.byref(rect))
return rect
def filter_queries(self, text):
for query_ in self.queries:
if query_.startswith(text):
yield query_
def initialize(self):
self.listbox.addStrings(self.filtered_queries)
def onIdOk(self):
self.queries[self.filtered_queries[0]]()
self.terminate()
def on_change(self):
text = self.textbox.getText()
self.listbox.clear()
if text:
self.filtered_queries = list(self.filter_queries(text))
self.listbox.addStrings(self.filtered_queries)
else:
self.listbox.addStrings(self.queries.keys())
def select_current_string(self, copy=False):
query = self.language.query("(string) @_")
captures = query.captures(self.tree.root_node)
current_pos = editor.getCurrentPos()
for node, _ in captures:
if node.start_byte <= current_pos <= node.end_byte:
if copy:
string = editor.getText()[node.start_byte:node.end_byte]
editor.copyText(string)
else:
editor.setSel(node.start_byte, node.end_byte)
def select_current_function(self, copy=False):
query = self.language.query("(function_definition) @_")
captures = query.captures(self.tree.root_node)
current_pos = editor.getCurrentPos()
for node, _ in captures:
if node.start_byte <= current_pos <= node.end_byte:
if copy:
string = editor.getText()[node.start_byte:node.end_byte]
editor.copyText(string)
else:
editor.setSel(node.start_byte, node.end_byte)
def add_docstring_to_current_function(self):
docstring_template = '''"""
{0}
{0} Args:
{0} {1}
{0} Returns:
{0}"""
{0}'''
indent = 4
query = self.language.query(
"""
(function_definition
name: (identifier) @function
parameters: (parameters) @function.parameters
body: (block) @function.body)
"""
)
captures = query.captures(self.tree.root_node)
functions = []
for node, capture in captures:
match capture:
case "function":
function_start_position = editor.positionFromLine(node.start_point[0])
function_name = node.text.decode()
case "function.parameters":
parameters = node.text.decode()
case "function.body":
function_info = {
"name": function_name,
"function_start_position": function_start_position,
"parameters": parameters,
"body_start_position": editor.positionFromLine(node.start_point[0]) + node.start_point[1],
"indent_level": node.start_point[1] // indent,
"body_end_position": editor.positionFromLine(node.end_point[0]) + node.end_point[1],
}
functions.append(function_info)
case _ :
continue
current_pos = editor.getCurrentPos()
for func in functions:
if func['function_start_position'] <= current_pos <= func['body_end_position']:
editor.insertText(func['body_start_position'], f'{docstring_template.format(" "*indent*func["indent_level"], func["parameters"])}')
break
QueryLanguage()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment