[IDE tool] comment toggle

Working on new editor enhancements?
AZJIO
Addict
Addict
Posts: 2154
Joined: Sun May 14, 2017 1:48 am

[IDE tool] comment toggle

Post by AZJIO »

I'm used to using one key to switch comments in Notepad++, so I wasn't used to using two different keys. In AkelPad I also use one key Ctrl+Q.
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()