Edit Control With Syntax Highlighting based on RichEdit control

Post your working scripts, libraries and tools.
Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Edit Control With Syntax Highlighting based on RichEdit control

03 Mar 2024, 08:25

Hi there.

This code has been written by G33kDude https://github.com/G33kDude/RichCode.ahk/tree/v2-main. I needed an edit control that supported Syntax Highlighting for Javascript, so I adapted the code a little bit and put everything needed in one file. Maybe it comes in handy for somebody else. If you need more/other languages (AHK, CSS, HTML), please take a look at the link above. The code below is only for Javascript Highlighting.

Edit 18.3.2024: corrected contextmenu to work as expected
Edit 19.3.2024: corrected wordwrap and OnMessage-Error when control has been deleted

Code: Select all

/*
MIT License

Copyright (c) 2017 GeekDude

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so
*/


#Requires AutoHotkey v2.0

; Settings array for the RichCode control
settings := {
	TabSize: 4,
	Indent: "`t",
	FGColor: 0xEDEDCD,
	BGColor: 0x3F3F3F,
	Font: {Typeface: "Consolas", Size: 11, Bold: false},
	WordWrap: False,

	UseHighlighter: True,
	HighlightDelay: 200,
	Colors: {
		Comments:     0x7F9F7F,
		Functions:    0x7CC8CF,
		Keywords:     0xE4EDED,
		Multiline:    0x7F9F7F,
		Numbers:      0xF79B57,
		Punctuation:  0x97C0EB,
		Strings:      0xCC9893,

		; AHK
		A_Builtins:   0xF79B57,
		Commands:     0xCDBFA3,
		Directives:   0x7CC8CF,
		Flow:         0xE4EDED,
		KeyNames:     0xCB8DD9,

		; CSS
		ColorCodes:   0x7CC8CF,
		Properties:   0xCDBFA3,
		Selectors:    0xE4EDED,

		; HTML
		Attributes:   0x7CC8CF,
		Entities:     0xF79B57,
		Tags:         0xCDBFA3,

		; JS
		Builtins:     0xE4EDED,
		Constants:    0xF79B57,
		Declarations: 0xCDBFA3
	}
}

; Add some controls
g := Gui()
g.AddButton("ym", "Block &Comment").OnEvent("Click", BlockComment)
g.AddButton("ym", "Block &Uncomment").OnEvent("Click", BlockUncomment)
g.OnEvent("Close", GuiClose)

; Add the RichCode
rc := RichCode(g, settings, "xm w640 h470")
rc.Settings.Highlighter := HighlightJS

; Set its starting contents

g.Show()


GuiClose(*) {
	global rc

	; Overwrite rc, leaving the only reference from the GUI
	rc := ""

	; Destroy the GUI, freeing the RichCode instance
	g.Destroy()

	; Close the script
	ExitApp
}


BlockComment(*) {
	rc.SelectedText := "/* " rc.SelectedText " */"
}

BlockUncomment(*) {
	rc.SelectedText := RegExReplace(rc.SelectedText, "s)\/\* ?(.+?) ?\*\/", "$1")
}









































; courtesy of G33KDUDE - https://github.com/G33kDude/RichCode.ahk/tree/v2-main
HighlightJS(Settings, &Code) {
	; Thank you to the Rouge project for compiling these keyword lists
	; https://github.com/jneen/rouge/blob/master/lib/rouge/lexers/javascript.rb
	static Keywords := "for|in|of|while|do|break|return|continue|switch|case|default|if|else|throw|try|catch|finally|new|delete|typeof|instanceof|void|this|yield|import|export|from|as|async|super|this"
	, Declarations := "var|let|const|with|function|class|extends|constructor|get|set"
	, Constants := "true|false|null|NaN|Infinity|undefined"
	, Builtins := "Array|Boolean|Date|Error|Function|Math|netscape|Number|Object|Packages|RegExp|String|sun|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|Error|eval|isFinite|isNaN|parseFloat|parseInt|document|window|console|navigator|self|global|Promise|Set|Map|WeakSet|WeakMap|Symbol|Proxy|Reflect|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Uint16ClampedArray|Int32Array|Uint32Array|Uint32ClampedArray|Float32Array|Float64Array|DataView|ArrayBuffer"
    , Needle := (						; opens a "continuation by enclosure" for better readability
        "ims)"							; options for the regex needle: i=caseinsensitive  m=multiline  s=DotAll  )=end of options      see https://www.autohotkey.com/docs/v2/misc/RegEx-QuickRef.htm
        "(\/\/[^\n]+)"               	; Comments
        "|(\/\*.*?\*\/)"             	; Multiline comments
        "|([+*!~&\/\\<>^|=?:@;"      	; Punctuation
        ",().```%{}\[\]\-]+)"        	; Punctuation (continued)
        "|\b(0x[0-9a-fA-F]+|[0-9]+)" 	; Numbers
        "|(`"[^`"]*`"|'[^']*')"      	; Strings
        "|\b(" Constants ")\b"       	; Constants
        "|\b(" Keywords ")\b"        	; Keywords
        "|\b(" Declarations ")\b"    	; Declarations
        "|\b(" Builtins ")\b"        	; Builtins
        "|(([a-zA-Z_$]+)(?=\())"     	; Functions
    )									; closes the "continuation by enclosure"


	GenHighlighterCache(Settings)
	ColMap := Settings.Cache.ColorMap

	RTF := ""

	Pos := 1
	while FoundPos := RegExMatch(Code, Needle, &Match, Pos) {
		RTF .= (													;continuation by enclosure
			"\cf" ColMap.Plain " "
			EscapeRTF(SubStr(Code, Pos, FoundPos - Pos))
			"\cf" (
				Match.1 ? ColMap.Comments :
				Match.2 ? ColMap.Multiline :
				Match.3 ? ColMap.Punctuation :
				Match.4 ? ColMap.Numbers :
				Match.5 ? ColMap.Strings :
				Match.6 ? ColMap.Constants :
				Match.7 ? ColMap.Keywords :
				Match.8 ? ColMap.Declarations :
				Match.9 ? ColMap.Builtins :
				Match.10 ? ColMap.functions :
				ColMap.Plain
			) " "
			EscapeRTF(Match.0)
		)
		Pos := FoundPos + Match.Len()
	}

	return (
		Settings.Cache.RTFHeader
		RTF
		"\cf" ColMap.Plain " "
		EscapeRTF(SubStr(Code, Pos))
		"\`n}"
	)
}




GenHighlighterCache(Settings)
{
	if Settings.HasOwnProp("Cache")
		return
	Cache := Settings.Cache := {}


	; --- Process Colors ---
	Cache.Colors := Settings.Colors.Clone()

	; Inherit from the Settings array's base
	BaseSettings := Settings
	while (BaseSettings := BaseSettings.Base)
		if BaseSettings.HasProp("Colors")
			for Name, Color in BaseSettings.Colors.OwnProps()
				if !Cache.Colors.HasProp(Name)
					Cache.Colors.%Name% := Color

	; Include the color of plain text
	if !Cache.Colors.HasOwnProp("Plain")
		Cache.Colors.Plain := Settings.FGColor

	; Create a Name->Index map of the colors
	Cache.ColorMap := {}
	for Name, Color in Cache.Colors.OwnProps()
		Cache.ColorMap.%Name% := A_Index


	; --- Generate the RTF headers ---
	RTF := "{\urtf"

	; Color Table
	RTF .= "{\colortbl;"
	for Name, Color in Cache.Colors.OwnProps()
	{
		RTF .= "\red"   Color>>16 & 0xFF
		RTF .= "\green" Color>>8  & 0xFF
		RTF .= "\blue"  Color     & 0xFF ";"
	}
	RTF .= "}"

	; Font Table
	if Settings.Font
	{
		FontTable .= "{\fonttbl{\f0\fmodern\fcharset0 "
		FontTable .= Settings.Font.Typeface
		FontTable .= ";}}"
		RTF .= "\fs" Settings.Font.Size * 2 ; Font size (half-points)
		if Settings.Font.Bold
			RTF .= "\b"
	}

	; Tab size (twips)
	RTF .= "\deftab" GetCharWidthTwips(Settings.Font) * Settings.TabSize

	Cache.RTFHeader := RTF
}

GetCharWidthTwips(Font)
{
	static Cache := Map()

	if Cache.Has(Font.Typeface "_" Font.Size "_" Font.Bold)
		return Cache[Font.Typeface "_" font.Size "_" Font.Bold]

	; Calculate parameters of CreateFont
	Height := -Round(Font.Size*A_ScreenDPI/72)
	Weight := 400+300*(!!Font.Bold)
	Face := Font.Typeface

	; Get the width of "x"
	hDC := DllCall("GetDC", "UPtr", 0)
	hFont := DllCall("CreateFont"
	, "Int", Height ; _In_ int     nHeight,
	, "Int", 0      ; _In_ int     nWidth,
	, "Int", 0      ; _In_ int     nEscapement,
	, "Int", 0      ; _In_ int     nOrientation,
	, "Int", Weight ; _In_ int     fnWeight,
	, "UInt", 0     ; _In_ DWORD   fdwItalic,
	, "UInt", 0     ; _In_ DWORD   fdwUnderline,
	, "UInt", 0     ; _In_ DWORD   fdwStrikeOut,
	, "UInt", 0     ; _In_ DWORD   fdwCharSet, (ANSI_CHARSET)
	, "UInt", 0     ; _In_ DWORD   fdwOutputPrecision, (OUT_DEFAULT_PRECIS)
	, "UInt", 0     ; _In_ DWORD   fdwClipPrecision, (CLIP_DEFAULT_PRECIS)
	, "UInt", 0     ; _In_ DWORD   fdwQuality, (DEFAULT_QUALITY)
	, "UInt", 0     ; _In_ DWORD   fdwPitchAndFamily, (FF_DONTCARE|DEFAULT_PITCH)
	, "Str", Face   ; _In_ LPCTSTR lpszFace
	, "UPtr")
	hObj := DllCall("SelectObject", "UPtr", hDC, "UPtr", hFont, "UPtr")
	size := Buffer(8, 0)
	DllCall("GetTextExtentPoint32", "UPtr", hDC, "Str", "x", "Int", 1, "Ptr", SIZE)
	DllCall("SelectObject", "UPtr", hDC, "UPtr", hObj, "UPtr")
	DllCall("DeleteObject", "UPtr", hFont)
	DllCall("ReleaseDC", "UPtr", 0, "UPtr", hDC)

	; Convert to twpis
	Twips := Round(NumGet(size, 0, "UInt")*1440/A_ScreenDPI)
	Cache[Font.Typeface "_" Font.Size "_" Font.Bold] := Twips
	return Twips
}

EscapeRTF(Code)
{
	for Char in ["\", "{", "}", "`n"]
		Code := StrReplace(Code, Char, "\" Char)
	return StrReplace(StrReplace(Code, "`t", "\tab "), "`r")
}


































/*
	BY G33KDUDE - https://github.com/G33kDude/RichCode.ahk/tree/v2-main

	class RichCode({"TabSize": 4     ; Width of a tab in characters
		, "Indent": "`t"             ; What text to insert on indent
		, "FGColor": 0xRRGGBB        ; Foreground (text) color
		, "BGColor": 0xRRGGBB        ; Background color
		, "Font"                     ; Font to use
		: {"Typeface": "Courier New" ; Name of the typeface
			, "Size": 12             ; Font size in points
			, "Bold": False}         ; Bolded (True/False)


		; Whether to use the highlighter, or leave it as plain text
		, "UseHighlighter": True

		; Delay after typing before the highlighter is run
		, "HighlightDelay": 200

		; The highlighter function (FuncObj or name)
		; to generate the highlighted RTF. It will be passed
		; two parameters, the first being this settings array
		; and the second being the code to be highlighted
		, "Highlighter": Func("HighlightAHK")

		; The colors to be used by the highlighter function.
		; This is currently used only by the highlighter, not at all by the
		; RichCode class. As such, the RGB ordering is by convention only.
		; You can add as many colors to this array as you want.
		, "Colors"
		: [0xRRGGBB
			, 0xRRGGBB
			, 0xRRGGBB,
			, 0xRRGGBB]})
*/

class RichCode
{
	#DllLoad "msftedit.dll"
	static IID_ITextDocument := "{8CC497C0-A1DF-11CE-8098-00AA0047BE5D}"
	static MenuItems := ["Cut", "Copy", "Paste", "Delete", "", "Select All", ""
		, "UPPERCASE", "lowercase", "TitleCase"]

	_Frozen := False

	/* @type {Gui.Custom} the underlying control */
	_control := {}

	Settings := {}

	gutter := { Hwnd: 0 }

	; --- Static Methods ---

	static BGRFromRGB(RGB) => RGB >> 16 & 0xFF | RGB & 0xFF00 | RGB << 16 & 0xFF0000

	; --- Properties ---

	Text {
		get => StrReplace(this._control.Text, "`r")
		set => (this.Highlight(Value), Value)
	}

	; TODO: reserve and reuse memory
	selection[i := 0] {
		get => (
			this.SendMsg(0x434, 0, charrange := Buffer(8)), ; EM_EXGETSEL
			out := [NumGet(charrange, 0, "Int"), NumGet(charrange, 4, "Int")],
			i ? out[i] : out
		)

		set => (
			i ? (t := this.selection, t[i] := Value, Value := t) : "",
			NumPut("Int", Value[1], "Int", Value[2], charrange := Buffer(8)),
			this.SendMsg(0x437, 0, charrange), ; EM_EXSETSEL
			Value
		)
	}

	SelectedText {
		get {
			Selection := this.selection
			length := selection[2] - selection[1]
			b := Buffer((length + 1) * 2)
			if this.SendMsg(0x43E, 0, b) > length ; EM_GETSELTEXT
				throw Error("Text larger than selection! Buffer overflow!")
			text := StrGet(b, length, "UTF-16")
			return StrReplace(text, "`r", "`n")
		}

		set {
			this.SendMsg(0xC2, 1, StrPtr(Value)) ; EM_REPLACESEL
			this.Selection[1] -= StrLen(Value)
			return Value
		}
	}

	EventMask {
		get => this._EventMask

		set {
			this._EventMask := Value
			this.SendMsg(0x445, 0, Value) ; EM_SETEVENTMASK
			return Value
		}
	}

	_UndoSuspended := false
	UndoSuspended {
		get {
			return this._UndoSuspended
		}

		set {
			try { ; ITextDocument is not implemented in WINE
				if Value
					this.ITextDocument.Undo(-9999995) ; tomSuspend
				else
					this.ITextDocument.Undo(-9999994) ; tomResume
			}
			return this._UndoSuspended := !!Value
		}
	}

	Frozen {
		get => this._Frozen

		set {
			if (Value && !this._Frozen)
			{
				try ; ITextDocument is not implemented in WINE
					this.ITextDocument.Freeze()
				catch
					this._control.Opt "-Redraw"
			}
			else if (!Value && this._Frozen)
			{
				try ; ITextDocument is not implemented in WINE
					this.ITextDocument.Unfreeze()
				catch
					this._control.Opt "+Redraw"
			}
			return this._Frozen := !!Value
		}
	}

	Modified {
		get {
			return this.SendMsg(0xB8, 0, 0) ; EM_GETMODIFY
		}

		set {
			this.SendMsg(0xB9, Value, 0) ; EM_SETMODIFY
			return Value
		}
	}

	; --- Construction, Destruction, Meta-Functions ---

	__New(gui, Settings, Options := "")
	{
		this.__Set := this.___Set
		this.Settings := Settings
		FGColor := RichCode.BGRFromRGB(Settings.FGColor)
		BGColor := RichCode.BGRFromRGB(Settings.BGColor)

		this._control := gui.AddCustom("ClassRichEdit50W +0x5031b1c4 +E0x20000 " Options)

		; Enable WordWrap in RichEdit control ("WordWrap" : true)
		if this.Settings.HasProp("WordWrap") && this.Settings.WordWrap
			this.SendMsg(0x448, 0, 0)

		; Register for WM_COMMAND and WM_NOTIFY events
		; NOTE: this prevents garbage collection of
		; the class until the control is destroyed
		this.EventMask := 1 ; ENM_CHANGE
		this._control.OnCommand 0x300, this.CtrlChanged.Bind(this)

		; Set background color
		this.SendMsg(0x443, 0, BGColor) ; EM_SETBKGNDCOLOR

		; Set character format
		f := settings.font
		cf2 := Buffer(116, 0)
		NumPut("UInt", 116, cf2, 0)          ; cbSize      = sizeof(CF2)
		NumPut("UInt", 0xE << 28, cf2, 4)    ; dwMask      = CFM_COLOR|CFM_FACE|CFM_SIZE
		NumPut("UInt", f.Size * 20, cf2, 12) ; yHeight     = twips
		NumPut("UInt", fgColor, cf2, 20) ; crTextColor = 0xBBGGRR
		StrPut(f.Typeface, cf2.Ptr + 26, 32, "UTF-16") ; szFaceName = TCHAR
		SendMessage(0x444, 0, cf2, this.Hwnd) ; EM_SETCHARFORMAT

		; Set tab size to 4 for non-highlighted code
		tabStops := Buffer(4)
		NumPut("UInt", Settings.TabSize * 4, tabStops)
		this.SendMsg(0x0CB, 1, tabStops) ; EM_SETTABSTOPS

		; Change text limit from 32,767 to max
		this.SendMsg(0x435, 0, -1) ; EM_EXLIMITTEXT

		; Bind for keyboard events
		; Use a pointer to prevent reference loop
		this.OnMessageBound := this.OnMessage.Bind(this)
		OnMessage(0x100, this.OnMessageBound) ; WM_KEYDOWN
		OnMessage(0x205, this.OnMessageBound) ; WM_RBUTTONUP

		; Bind the highlighter
		this.HighlightBound := this.Highlight.Bind(this)

		; Create the right click menu
		this.menu := Menu()
		for Index, Entry in RichCode.MenuItems
			(entry == "") ? this.menu.Add() : this.menu.Add(Entry, this.RightClickMenu.Bind(this))

		; Get the ITextDocument object
		bufpIRichEditOle := Buffer(A_PtrSize, 0)
		this.SendMsg(0x43C, 0, bufpIRichEditOle) ; EM_GETOLEINTERFACE
		this.pIRichEditOle := NumGet(bufpIRichEditOle, "UPtr")
		this.IRichEditOle := ComValue(9, this.pIRichEditOle, 1)
		; ObjAddRef(this.pIRichEditOle)
		this.pITextDocument := ComObjQuery(this.IRichEditOle, RichCode.IID_ITextDocument)
		this.ITextDocument := ComValue(9, this.pITextDocument, 1)
		; ObjAddRef(this.pITextDocument)
	}

	RightClickMenu(ItemName, ItemPos, MenuName, *)
	{
		if (ItemName == "Cut")
			A_Clipboard := this.SelectedText, this.SelectedText := ""
		else if (ItemName == "Copy")
			A_Clipboard := this.SelectedText
		else if (ItemName == "Paste")
			this.SelectedText := A_Clipboard
		else if (ItemName == "Delete")
			this.SelectedText := ""
		else if (ItemName == "Select All")
			this.Selection := [0, -1]
		else if (ItemName == "UPPERCASE")
			this.SelectedText := Format("{:U}", this.SelectedText)
		else if (ItemName == "lowercase")
			this.SelectedText := Format("{:L}", this.SelectedText)
		else if (ItemName == "TitleCase")
			this.SelectedText := Format("{:T}", this.SelectedText)
	}

	__Delete()
	{
		; Release the ITextDocument object
		this.ITextDocument := unset, ObjRelease(this.pITextDocument)
		this.IRichEditOle := unset, ObjRelease(this.pIRichEditOle)

		; Release the OnMessage handlers
		OnMessage(0x100, this.OnMessageBound, 0) ; WM_KEYDOWN
		OnMessage(0x205, this.OnMessageBound, 0) ; WM_RBUTTONUP

		; Destroy the right click menu
		this.menu := unset
	}

	__Call(Name, Params) => this._control.%Name%(Params*)
	__Get(Name, Params) => this._control.%Name%[Params*]
	___Set(Name, Params, Value) {
		try {
			this._control.%Name%[Params*] := Value
		} catch Any as e {
			e2 := Error(, -1)
			e.What := e2.What
			e.Line := e2.Line
			e.File := e2.File
			throw e
		}
	}

	; --- Event Handlers ---

	OnMessage(wParam, lParam, Msg, hWnd)
	{
		try {
			if (hWnd != this._control.hWnd)
				return
		} catch {
			return
		}

		if (Msg == 0x100) ; WM_KEYDOWN
		{
			if (wParam == GetKeyVK("Tab"))
			{
				; Indentation
				Selection := this.Selection
				if GetKeyState("Shift")
					this.IndentSelection(True) ; Reverse
				else if (Selection[2] - Selection[1]) ; Something is selected
					this.IndentSelection()
				else
				{
					; TODO: Trim to size needed to reach next TabSize
					this.SelectedText := this.Settings.Indent
					this.Selection[1] := this.Selection[2] ; Place cursor after
				}
				return False
			}
			else if (wParam == GetKeyVK("Escape")) ; Normally closes the window
				return False
			else if (wParam == GetKeyVK("v") && GetKeyState("Ctrl"))
			{
				this.SelectedText := A_Clipboard ; Strips formatting
				this.Selection[1] := this.Selection[2] ; Place cursor after
				return False
			}
		}
		else if (Msg == 0x205) ; WM_RBUTTONUP
		{
			this.menu.Show()
			return False
		}
	}

	CtrlChanged(control)
	{
		; Delay until the user is finished changing the document
		SetTimer this.HighlightBound, -Abs(this.Settings.HighlightDelay)
	}

	; --- Methods ---

	; First parameter is taken as a replacement value
	; Variadic form is used to detect when a parameter is given,
	; regardless of content
	Highlight(NewVal := unset)
	{
		if !(this.Settings.UseHighlighter && this.Settings.Highlighter) {
			if IsSet(NewVal)
				this._control.Text := NewVal
			return
		}

		; Freeze the control while it is being modified, stop change event
		; generation, suspend the undo buffer, buffer any input events
		PrevFrozen := this.Frozen, this.Frozen := True
		PrevEventMask := this.EventMask, this.EventMask := 0 ; ENM_NONE
		PrevUndoSuspended := this.UndoSuspended, this.UndoSuspended := True
		PrevCritical := Critical(1000)

		; Run the highlighter
		Highlighter := this.Settings.Highlighter
		if !IsSet(NewVal)
			NewVal := this.text
		RTF := Highlighter(this.Settings, &NewVal)

		; "TRichEdit suspend/resume undo function"
		; https://stackoverflow.com/a/21206620


		; Save the rich text to a UTF-8 buffer
		buf := Buffer(StrPut(RTF, "UTF-8"))
		StrPut(RTF, buf, "UTF-8")

		; Set up the necessary structs
		zoom := Buffer(8, 0) ; Zoom Level
		point := Buffer(8, 0) ; Scroll Pos
		charrange := Buffer(8, 0) ; Selection
		settextex := Buffer(8, 0) ; SetText settings
		NumPut("UInt", 1, settextex) ; flags = ST_KEEPUNDO

		; Save the scroll and cursor positions, update the text,
		; then restore the scroll and cursor positions
		MODIFY := this.SendMsg(0xB8, 0, 0)    ; EM_GETMODIFY
		this.SendMsg(0x4E0, ZOOM.ptr, ZOOM.ptr + 4)   ; EM_GETZOOM
		this.SendMsg(0x4DD, 0, POINT)        ; EM_GETSCROLLPOS
		this.SendMsg(0x434, 0, CHARRANGE)    ; EM_EXGETSEL
		this.SendMsg(0x461, SETTEXTEX, Buf) ; EM_SETTEXTEX
		this.SendMsg(0x437, 0, CHARRANGE)    ; EM_EXSETSEL
		this.SendMsg(0x4DE, 0, POINT)        ; EM_SETSCROLLPOS
		this.SendMsg(0x4E1, NumGet(ZOOM, "UInt")
			, NumGet(ZOOM, 4, "UInt"))        ; EM_SETZOOM
		this.SendMsg(0xB9, MODIFY, 0)         ; EM_SETMODIFY

		; Restore previous settings
		Critical PrevCritical
		this.UndoSuspended := PrevUndoSuspended
		this.EventMask := PrevEventMask
		this.Frozen := PrevFrozen
	}

	IndentSelection(Reverse := False, Indent := unset) {
		; Freeze the control while it is being modified, stop change event
		; generation, buffer any input events
		PrevFrozen := this.Frozen
		this.Frozen := True
		PrevEventMask := this.EventMask
		this.EventMask := 0 ; ENM_NONE
		PrevCritical := Critical(1000)

		if !IsSet(Indent)
			Indent := this.Settings.Indent
		IndentLen := StrLen(Indent)

		; Select back to the start of the first line
		sel := this.selection
		top := this.SendMsg(0x436, 0, sel[1]) ; EM_EXLINEFROMCHAR
		bottom := this.SendMsg(0x436, 0, sel[2]) ; EM_EXLINEFROMCHAR
		this.Selection := [
			this.SendMsg(0xBB, top, 0), ; EM_LINEINDEX
			this.SendMsg(0xBB, bottom + 1, 0) - 1 ; EM_LINEINDEX
		]

		; TODO: Insert newlines using SetSel/ReplaceSel to avoid having to call
		; the highlighter again
		Text := this.SelectedText
		out := ""
		if Reverse { ; Remove indentation appropriately
			loop parse text, "`n", "`r" {
				if InStr(A_LoopField, Indent) == 1
					Out .= "`n" SubStr(A_LoopField, 1 + IndentLen)
				else
					Out .= "`n" A_LoopField
			}
		} else { ; Add indentation appropriately
			loop parse Text, "`n", "`r"
				Out .= "`n" Indent . A_LoopField
		}
		this.SelectedText := SubStr(Out, 2)

		this.Highlight()

		; Restore previous settings
		Critical PrevCritical
		this.EventMask := PrevEventMask

		; When content changes cause the horizontal scrollbar to disappear,
		; unfreezing causes the scrollbar to jump. To solve this, jump back
		; after unfreezing. This will cause a flicker when that edge case
		; occurs, but it's better than the alternative.
		point := Buffer(8, 0)
		this.SendMsg(0x4DD, 0, POINT) ; EM_GETSCROLLPOS
		this.Frozen := PrevFrozen
		this.SendMsg(0x4DE, 0, POINT) ; EM_SETSCROLLPOS
	}

	; --- Helper/Convenience Methods ---

	SendMsg(Msg, wParam, lParam) =>
		SendMessage(msg, wParam, lParam, this._control.Hwnd)
}














Last edited by Spitzi on 19 Mar 2024, 12:36, edited 3 times in total.
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Edit Control With Syntax Highlighting based on RichEdit control

05 Mar 2024, 18:28

When you go to modify and re-share code developed by other people, please pay attention to how their code is licensed and make sure to follow the license terms. RichCode is licensed under the MIT license, which does allow redistribution with modification, but requires that
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
which you have not done here. Can you please update the original post to correctly follow the license terms?
Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: Edit Control With Syntax Highlighting based on RichEdit control

09 Mar 2024, 02:56

Hi @geek, sorry for this. Of course, I updated my post. I hope this is what you expected. Thanks again for your help!
Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: Edit Control With Syntax Highlighting based on RichEdit control

18 Mar 2024, 08:41

@geek Love your RichCode!!

I succeded at getting the context menu to work (see first post). But now I am stuck: I would like to register an event to the richtext control, e.g. "Change", like this

Code: Select all

		
anEditorGui.Add("Text", "y+40 vscriptText" , "A script:")
rc := RichCode(anEditorGui, RC_Settings, "xm y+5 w1500 r30 vscript")
rc.text := hEl.SelectSingleNode("script").text
rc.OnEvent("Change", (*) => MsgBox("test") )
It doesn't work. How would I implement such an event? Do I need to register a message? How would I do that? Thanks in advance.
Spitzi
Posts: 313
Joined: 24 Feb 2022, 03:45

Re: Edit Control With Syntax Highlighting based on RichEdit control

18 Mar 2024, 11:34

One more thing @geek:

When a gui containing a richCode control is destroyed, it seems like the Messages don't unregister propertly , and when the next key or rightMouseButton is pressed, this code part throws an error

Code: Select all

if (hWnd != this._control.hWnd)
	return
I really didn't know how to fix this properly, so in the end I changed it to:

Code: Select all

		
try {
	if (hWnd != this._control.hWnd)
		return
} catch {
	return
}
Also, the wordWrap was never set properly. I changed

Code: Select all

if this.Settings.HasOwnProp("WordWrap")
	this.SendMsg(0x448, 0, 0)
to

Code: Select all

if this.Settings.HasProp("WordWrap") && this.Settings.WordWrap
	this.SendMsg(0x448, 0, 0)
Maybe you want to update your GitHub Repository. Thanks again for this. Spitzi

Return to “Scripts and Functions (v2)”

Who is online

Users browsing this forum: No registered users and 115 guests