I made a code that switches comments using one key. The tool supports multi-line text. If in a multi-line text there is at least one line without a comment, then this text is considered as uncommented and will be commented out again for someone.
The algorithm is not complicated, it requests the beginning and end of the selected text and shifts the selection to the entire line if necessary. Then it checks to see if at least one line is uncommented. And then two functions. Since I couldn’t insert directly through the Scintilla object, I did it through the clipboard.
So far I have discovered a problem: once out of five, the captured text turns out to be not what is selected, it processes it accordingly and inserts garbage.
I made text capture via #SCI_COPY and it began to work reliably
Code: Select all
; AZJIO (comment_toggle)
EnableExplicit
;- ● Global
Global PbIdeHandle, ScintillaHandle, Selected$
CompilerSelect #PB_Compiler_OS
CompilerCase #PB_OS_Windows
Global CRLF$ = #CRLF$
CompilerCase #PB_OS_Linux
Global CRLF$ = #LF$
CompilerEndSelect
;- ● Declare
Declare IsComment(*c.Character)
; Declare DelComm(*c.Character)
Declare.s DelComm(text$)
Declare.s AddComm(text$)
; For test only
Global classText.s = Space(256)
; Finding a PureBasic Window
Procedure.l enumChildren0(hwnd.l)
If hwnd
GetClassName_(hwnd, @classText, 256)
If classText = "WindowClass_2"
GetWindowText_(hwnd, @classText, 256)
If Left(classText, 9) = "PureBasic"
PbIdeHandle = hwnd
ProcedureReturn 0
EndIf
EndIf
ProcedureReturn 1
EndIf
ProcedureReturn 0
EndProcedure
; Finding the Scintilla
Procedure.l enumChildren1(hwnd.l)
If hwnd
GetClassName_(hwnd, @classText, 256)
If classText = "Scintilla"
ScintillaHandle = hwnd
ProcedureReturn 0
EndIf
ProcedureReturn 1
EndIf
ProcedureReturn 0
EndProcedure
; End: For test only
Procedure.s GetScintillaRangeText(cursor, length)
Protected String$
; Protected length
Protected *mem
Protected PID
Protected hProcess
Protected *pointer
If length
*mem = AllocateMemory(length + 2)
If *mem
; высылаем сообщение ScintillaHandle, чтобы получить указатель (в *pointer) на позицию где находится "курсор"
SendMessageTimeout_(ScintillaHandle, #SCI_GETRANGEPOINTER, cursor, 0, #SMTO_ABORTIFHUNG, 2000, @*pointer)
If *pointer ; если указатель получен, то
GetWindowThreadProcessId_(ScintillaHandle, @PID) ; получаем идентификатор процесса ScintillaHandle
hProcess = OpenProcess_(#PROCESS_VM_READ | #SYNCHRONIZE, #False, PID) ; запрашиваем доступ к процессу и получаем декскриптор
If hProcess ; если дескриптор получен, то
; читаем память процесса по указателю *pointer в выделенную память *mem с заказанной длинной
ReadProcessMemory_(hProcess, *pointer, *mem, length, 0)
String$ = PeekS(*mem, -1, #PB_UTF8) ; а может тут стоит указать длину для чтения? Выделено на 2 байта больше чтобы -1
CloseHandle_(hProcess)
EndIf
EndIf
FreeMemory(*mem)
EndIf
EndIf
ProcedureReturn String$
EndProcedure
Procedure Initialization()
Protected LenSelText, pos1, pos2, line1, line2, *p
CompilerIf #PB_Compiler_Debugger
; For test only
EnumChildWindows_(0, @enumChildren0(), 0)
EnumChildWindows_(PbIdeHandle, @enumChildren1(), 0)
; End: For test only
CompilerElse
ScintillaHandle = Val(GetEnvironmentVariable("PB_TOOL_Scintilla"))
If ScintillaHandle = 0 : End : EndIf
CompilerEndIf
Debug ScintillaHandle
pos1 = SendMessage_(ScintillaHandle, #SCI_GETCURRENTPOS, 0, 0)
pos2 = SendMessage_(ScintillaHandle, #SCI_GETANCHOR, 0, 0)
If pos2 < pos1 ; если выделение в обратную сторону, то меняем местами, начало всегда должно быть первым
Swap pos1, pos2
EndIf
; Debug pos1
; Debug pos2
line1 = SendMessage_(ScintillaHandle, #SCI_LINEFROMPOSITION, pos1, 0) ; получаем номер строки
pos1 = SendMessage_(ScintillaHandle, #SCI_POSITIONFROMLINE, line1, 0) ; позиция начала указанной строки
; Debug "line: " + Str(line)
line2 = SendMessage_(ScintillaHandle, #SCI_LINEFROMPOSITION, pos2, 0) ; получаем номер строки
pos2 = SendMessage_(ScintillaHandle, #SCI_GETLINEENDPOSITION, line2, 0) ; позиция конца указанной строки
; выделить строки от начала до конца
SendMessage_(ScintillaHandle, #SCI_SETSELECTIONSTART, pos1, 0)
SendMessage_(ScintillaHandle, #SCI_SETSELECTIONEND, pos2, 0)
; захват через буфер обмена надёжный
SendMessage_(ScintillaHandle, #SCI_COPY, 0, 0)
Selected$ = GetClipboardText()
; захват текста через прямой доступ к процессу работает только для однобайтной кодировки, то есть для латинских символов
; LenSelText = SendMessage_(ScintillaHandle, #SCI_GETSELTEXT, 0, 0) ; размер буфера в байтах, чтобы уместить текст, не в символах.
; Selected$ = GetScintillaRangeText(pos1, LenSelText) ; а возвращаем в юнкоде, не в UTF8
Debug LenSelText
; Debug pos1
; Debug pos2
Debug pos2 - pos1
; Debug Selected$
Protected Log
Log = 0
If Log
MessageRequester("", Selected$)
EndIf
; Проверить есть ли хоть одна не закоментированная строка. Если есть, то блок требует добавление закоментирования во все строки
; Debug "IsComment = vvvv"
; Debug IsComment(@Selected$) ; проверяет являются ли все строки закомментированными.
If IsComment(@Selected$)
; Если да то убираем коментированность
Selected$ = DelComm(Selected$)
; DelComm2(@Selected$)
Else
; если нет, то добавляем
Selected$ = AddComm(Selected$)
EndIf
Debug Selected$
; 1 сбой, видимо SendMessage не подходит для сложной функции
; If MessageRequester("Текст для вставки", "|" + Selected$ + "|", #PB_MessageRequester_YesNo) = #PB_MessageRequester_Yes
; *p = UTF8(Selected$)
; SendMessage_(ScintillaHandle, #SCI_REPLACESEL, 0, *p)
; FreeMemory(*p)
; EndIf
; 2 сбой, видимо SendMessage не подходит для сложной функции
; SendMessage_(ScintillaHandle, #SCI_SETTARGETRANGE, pos2, pos1) ; установили цель для замены
; *p = UTF8(Selected$)
; SendMessage_(ScintillaHandle, #SCI_REPLACETARGET, -1, *p)
; FreeMemory(*p)
; 3 Жаль, что приходится использовать буфер обмена.
If Log
MessageRequester("", Selected$)
EndIf
If Asc(Selected$)
SetClipboardText(Selected$)
Delay(40)
SendMessage_(ScintillaHandle, #SCI_PASTE, 0, 0)
Delay(40)
; Восстановим выделение, чтобы легко переключить обратно.
pos1 = SendMessage_(ScintillaHandle, #SCI_POSITIONFROMLINE, line1, 0) ; позиция начала указанной строки
pos2 = SendMessage_(ScintillaHandle, #SCI_GETLINEENDPOSITION, line2, 0) ; позиция конца указанной строки
; выделить строки от начала до конца
SendMessage_(ScintillaHandle, #SCI_SETSELECTIONSTART, pos1, 0)
SendMessage_(ScintillaHandle, #SCI_SETSELECTIONEND, pos2, 0)
EndIf
EndProcedure
; AZJIO
; https://www.purebasic.fr/english/viewtopic.php?p=585485#p585485
Procedure SplitL2(String$, List StringList.s(), Separator$ = #CRLF$ + #TAB$ + #FF$ + #VT$ + " ")
Protected *S.Integer = @String$
Protected *jc.Character, *c.Character = @String$
ClearList(StringList())
While *c\c
*jc = @Separator$
While *jc\c
If *c\c = *jc\c
*c\c = 0
If *S <> *c
AddElement(StringList())
StringList() = PeekS(*S)
; StringList() = PeekS(*S, (*c - *S) >> 1)
EndIf
*S = *c + SizeOf(Character)
Break
EndIf
*jc + SizeOf(Character)
Wend
*c + SizeOf(Character)
Wend
AddElement(StringList())
StringList() = PeekS(*S)
EndProcedure
; wilbert
; https://www.purebasic.fr/english/viewtopic.php?f=12&t=65159&p=486382&hilit=SplitL#p486382
Procedure SplitL(String.s, List StringList.s(), Separator.s = " ")
Protected S.String, *S.Integer = @S
Protected.i p, slen
slen = Len(Separator)
ClearList(StringList())
*S\i = @String
Repeat
AddElement(StringList())
p = FindString(S\s, Separator)
StringList() = PeekS(*S\i, p - 1)
*S\i + (p + slen - 1) << #PB_Compiler_Unicode
Until p = 0
*S\i = 0
EndProcedure
Procedure.s AddComm(text$)
Protected NewList String$()
SplitL(text$, String$(), CRLF$)
text$ = ""
ForEach String$()
If Asc(String$())
text$ + "; " + String$() + CRLF$
Else
text$ + CRLF$
EndIf
Next
text$ = Left(text$, Len(text$) - 2)
ProcedureReturn text$
EndProcedure
Procedure.s DelComm(text$)
Protected Pos, part1$, part2$
Protected NewList String$()
SplitL(text$, String$(), CRLF$)
text$ = ""
ForEach String$()
Pos = FindString(String$(), ";")
If Pos
part1$ = Left(String$(), Pos - 1)
If Mid(String$(), Pos + 1, 1) = " "
part2$ = Mid(String$(), Pos + 2)
Else
part2$ = Mid(String$(), Pos + 1)
EndIf
String$() = part1$ + part2$
EndIf
text$ + String$() + CRLF$
Next
text$ = Left(text$, Len(text$) - 2)
ProcedureReturn text$
EndProcedure
Procedure DelComm2(*c.Character)
Protected flgComm
If *c = 0 Or *c\c = 0
ProcedureReturn 0
EndIf
While *c\c
Select *c\c
Case ' ', #TAB
; ничего не делаем, даём циклу увеличить *c
Case #CR, #LF
; ничего не делаем, даём циклу увеличить *c, скорее всего была пустая строка
flgComm = 0
Case ';'
; If Not flgComm
flgComm = 1 ; Найдена кавычка, следующие данные являются закомментированными до следующей строки
*c\c = ' '
; EndIf
Default ; если любой другой текст, то либо проскакиваем, либо выпрыгиваем
If flgComm
Repeat
*c + SizeOf(Character)
Until *c\c = 0 Or *c\c = #CR Or *c\c = #LF
flgComm = 0
; Else
; flag = #False
; Debug "ага"
; Break
EndIf
EndSelect
*c + SizeOf(Character)
Wend
EndProcedure
Procedure IsComment(*c.Character)
Protected flgComm, flag = #True
If *c = 0 Or *c\c = 0
ProcedureReturn 0
EndIf
While *c\c
Select *c\c
Case ' ', #TAB
; ничего не делаем, даём циклу увеличить *c
Case #CR, #LF
; ничего не делаем, даём циклу увеличить *c, скорее всего была пустая строка
flgComm = 0
Case ';'
flgComm = 1 ; Найдена кавычка, следующие данные являются закомментированными до следующей строки
Default ; если любой другой текст, то либо проскакиваем, либо выпрыгиваем
If flgComm
Repeat
*c + SizeOf(Character)
Until *c\c = 0 Or *c\c = #CR Or *c\c = #LF
flgComm = 0
Else
flag = #False
Debug "ага"
Break
EndIf
EndSelect
*c + SizeOf(Character)
Wend
ProcedureReturn flag
EndProcedure
Initialization()