@rb it is possible via a script defined as a Pythonista tool, adding an additional key in Pythonista keyboard.
Example here-under only shows how to find next ending character, not really the matching pair, but this script only to show the way.
Put your cusor on a ( or [ or {, tap the "pair" button

and you will get this

To erase special backgrounds, tap pair with cursor on any other character
import editor
from objc_util import *
import ui
def key_pressed(sender):
import ui
from objc_util import ObjCClass
tv = sender.objc_instance.firstResponder() # associated TextView
# get actual cursor position
cursor = tv.offsetFromPosition_toPosition_(tv.beginningOfDocument(), tv.selectedTextRange().start())
if sender.name == 'pair':
for sv in tv.subviews():
if 'SUIButton_PY3' in str(sv._get_objc_classname()):
sv.removeFromSuperview()
pairs = {'(':')','[':']','{':'}'}
t = str(tv.text())[cursor]
if t not in pairs:
return
ts = pairs[t]
t = str(tv.text())[cursor+1:]
cursor += 1
while True:
try:
t = str(tv.text())[cursor]
except:
return
if t == ts:
p1 = tv.positionFromPosition_offset_(tv.beginningOfDocument(), cursor)
p2 = tv.positionFromPosition_offset_(tv.beginningOfDocument(), cursor+1)
rge = tv.textRangeFromPosition_toPosition_(p1,p2)
rect = tv.firstRectForRange_(rge) # CGRect
x,y = rect.origin.x,rect.origin.y
w,h = rect.size.width,rect.size.height
l = ui.Button()
l.frame = (x,y,w,h)
l.background_color = (1,0,0,0.2)
l.corner_radius = 4
l.border_width = 1
tv.addSubview_(l)
break
cursor += 1
else:
# normal key
tv.insertText_(sender.title)
return
# set cursor
cursor_position = tv.positionFromPosition_offset_(tv.beginningOfDocument(), cursor)
tv.selectedTextRange = tv.textRangeFromPosition_toPosition_(cursor_position, cursor_position)
class MyView(ui.View):
def __init__(self, pad, *args, **kwargs):
#super().__init__(self, *args, **kwargs)
self.width = ui.get_screen_size()[0] # width of keyboard = screen
self.background_color = 'lightgray'#(0,1,0,0.2)
self.h_button = 32
self.pad = pad
# build buttons
for pad_elem in self.pad:
if pad_elem['key'] in ('nul', 'new row'): # free space or new row
continue
button = ui.Button() # Button for user functionnality
button.name = pad_elem['key']
button.background_color = 'white' # or any other color
button.tint_color = 'black'
button.corner_radius = 5
button.font = ('<system>',self.h_button - 8)
button.title = ''
if 'title' in pad_elem:
button.title = pad_elem['title']
elif 'icon' in pad_elem:
button.image = ui.Image.named(pad_elem['icon']).with_rendering_mode(ui.RENDERING_MODE_ORIGINAL)
else:
button.title = pad_elem['key']
button.action = key_pressed
retain_global(button) # see https://forum.omz-software.com/topic/4653/button-action-not-called-when-view-is-added-to-native-view
self.add_subview(button)
self.layout()
def layout(self):
import ui
#print('layout')
# supports changing orientation
#print(ui.get_screen_size())
dx = 8
dy = 2
x0 = 15
y0 = 10
dx_middle = 25
y = y0
x = x0
w_button = (ui.get_screen_size()[0] - 2*x0 - 17*dx - dx_middle)/18
for pad_elem in self.pad:
nw = pad_elem.get('width', 1)
wb = w_button*nw + dx*(nw-1)
if (x + wb + dx) > self.width:
y = y + self.h_button + dy
x = x0
if pad_elem['key'] == 'nul': # let free space
x = x + wb + dx
continue
elif pad_elem['key'] == 'new row': # new row
y = y + self.h_button + dy
x = x0
continue
button = self[pad_elem['key']]
xb = x + dx_middle if (x+wb) > self.width/2 else x
button.frame = (xb,y,wb,self.h_button)
if button.title != '':
font_size = self.h_button - 8
while True:
d = ui.measure_string(button.title,font=(button.font[0],font_size))[0]+4
if d <= wb:
break
font_size = font_size - 1
button.font = (button.font[0],font_size)
x = x + wb + dx
self.height = y + self.h_button + dy
@on_main_thread
def AddButtonsToPythonistaKeyboard(pad=None):
ev = editor._get_editor_tab().editorView()
tv = ev.textView()
#print(tv._get_objc_classname())
#print(dir(tv))
# create ui.View for InputAccessoryView above keyboard
v = MyView(pad) # view above keyboard
vo = ObjCInstance(v) # get ObjectiveC object of v
retain_global(v) # see https://forum.omz-software.com/topic/4653/button-action-not-called-when-view-is-added-to-native-view
tv.setInputAccessoryView_(vo) # attach accessory to textview
tv.strfind = ''
if __name__ == '__main__':
AddButtonsToPythonistaKeyboard(pad=[{'key':'pair'}])