GNU bug report logs - #74610
31.0.50; Submitting mhtml-ts-mode, treesitter alternative to mhtml-mode

Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.

Package: emacs; Severity: wishlist; Reported by: Vincenzo Pupillo <v.pupillo@HIDDEN>; Done: Juri Linkov <juri@HIDDEN>; Maintainer for emacs is bug-gnu-emacs@HIDDEN.
bug marked as fixed in version 31.0.50, send any further explanations to 74610 <at> debbugs.gnu.org and Vincenzo Pupillo <v.pupillo@HIDDEN> Request was from Juri Linkov <juri@HIDDEN> to control <at> debbugs.gnu.org. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 17 Feb 2025 07:24:59 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Mon Feb 17 02:24:59 2025
Received: from localhost ([127.0.0.1]:41923 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tjvUx-0000tz-FT
	for submit <at> debbugs.gnu.org; Mon, 17 Feb 2025 02:24:59 -0500
Received: from relay7-d.mail.gandi.net ([217.70.183.200]:55451)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <juri@HIDDEN>)
 id 1tjvUu-0000tE-W9; Mon, 17 Feb 2025 02:24:57 -0500
Received: by mail.gandi.net (Postfix) with ESMTPSA id 355EB441E7;
 Mon, 17 Feb 2025 07:24:47 +0000 (UTC)
From: Juri Linkov <juri@HIDDEN>
To: Vincenzo Pupillo <v.pupillo@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
In-Reply-To: <2164412.9o76ZdvQCi@fedora>
Organization: LINKOV.NET
References: <3532547.LZWGnKmheA@fedora> <2542357.XAFRqVoOGU@fedora>
 <87a5anjeuk.fsf@HIDDEN> <2164412.9o76ZdvQCi@fedora>
Date: Mon, 17 Feb 2025 09:23:45 +0200
Message-ID: <875xl9f30e.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/31.0.50 (x86_64-pc-linux-gnu)
MIME-Version: 1.0
Content-Type: text/plain
X-GND-State: clean
X-GND-Score: -100
X-GND-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgdehjeejiecutefuodetggdotefrodftvfcurfhrohhfihhlvgemucfitefpfffkpdcuggftfghnshhusghstghrihgsvgenuceurghilhhouhhtmecufedtudenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujfgurhephffvvefujghofhffkfgfgggtsehttdertddtredtnecuhfhrohhmpefluhhrihcunfhinhhkohhvuceojhhurhhisehlihhnkhhovhdrnhgvtheqnecuggftrfgrthhtvghrnhepffegteefveelhfeljeefueehieduiedtfffhuddtkeeffffghfevheetgeeukeehnecukfhppeeluddruddvledrleekrdehnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehinhgvthepledurdduvdelrdelkedrhedphhgvlhhopehmrghilhdrghgrnhguihdrnhgvthdpmhgrihhlfhhrohhmpehjuhhriheslhhinhhkohhvrdhnvghtpdhnsggprhgtphhtthhopeehpdhrtghpthhtoheptghonhhtrhholhesuggvsggsuhhgshdrghhnuhdrohhrghdprhgtphhtthhopeejgeeiuddtseguvggssghughhsrdhgnhhurdhorhhgpdhrtghpthhtohepvghlihiisehgnhhurdhorhhgpdhrtghpthhtoheptggrshhouhhrihesghhmrghilhdrtghomhdprhgtphhtthhopehvrdhpuhhpihhllhhosehgmhgrihhlrdgtohhm
X-GND-Sasl: juri@HIDDEN
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 74610
Cc: casouri@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

close 74610 31.0.50
thanks

>> Is it time now to push your patch?
>
> Yes!

Thank you for this much needed mode!

Now pushed to master and closed.
Probably would be better to continue its development
by creating new feature requests such as for
implementing TODO for treesit-defun-type-regexp to be
an aggregated version, also aggregation for outlines,
etc.




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 16 Feb 2025 14:26:02 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 16 09:26:02 2025
Received: from localhost ([127.0.0.1]:33005 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tjfas-00014y-7U
	for submit <at> debbugs.gnu.org; Sun, 16 Feb 2025 09:26:02 -0500
Received: from mail-wm1-x32a.google.com ([2a00:1450:4864:20::32a]:44520)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <v.pupillo@HIDDEN>)
 id 1tjfap-00014X-E6
 for 74610 <at> debbugs.gnu.org; Sun, 16 Feb 2025 09:26:00 -0500
Received: by mail-wm1-x32a.google.com with SMTP id
 5b1f17b1804b1-43961c966a8so21962435e9.1
 for <74610 <at> debbugs.gnu.org>; Sun, 16 Feb 2025 06:25:59 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1739715953; x=1740320753; darn=debbugs.gnu.org;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:from:to:cc:subject:date
 :message-id:reply-to;
 bh=7ebW8Iv8/o6JX9Nsy7lL4XHmlbRWf68jX/k0ABv49Vc=;
 b=HvyhnSU/JXPdf1xLKM8DKaw1sNo7kIiAanHTtlhFJa6fatBYQLY1VQS2vpSk/Wq3Qb
 /znE7oLhtAL7GI9IzSR9a43Cnfy+CsKZJX9GxN4ZIIZ7Kxv2W9n0kKpspzz4GL5xboZ4
 cvY52LywvkoDhcU3nHfI9y1uJDjWGfV2q30aeurRW8SYVtwbURj0PA33WNxOSrwd1mAf
 uOVmI9TvJp2RDGW0uzPE+99Oo+8ngz1Pht14O9X+wIaBc6xcUxLiwBN5pwem2DHKAZH9
 WqxULky3OVHqV5YJjyVKXnkZf2EVaJ7J5NwSvT128MFIZITGQfIHQWdPmYLmozfubpg4
 3xXQ==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1739715953; x=1740320753;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=7ebW8Iv8/o6JX9Nsy7lL4XHmlbRWf68jX/k0ABv49Vc=;
 b=qZ9Dg0lNlZH3YuSSATiux73rRRb47Sd5WQAyH/YvW8WGpZFGwQhH1OFy6MabBYtPAx
 9JUpwxos9ZENGWAq/rgGKC5qzmglJZh59XZ7N6eYYmFEAg6dpDNKxhGMjQyRiTcV0AKw
 16G38L/sYO3fBXQ2hx978b9yHXD37nxBiG6VWZnBHK9Q6ZKoehhZtA/Vfn7SNm2XyG/X
 WxVczhwMETHwDEQt2ftEBwXD0UqoXJ6oDUYzX1HirOMtRRltE6l4E1y49VE1KuCif5Rc
 DvcwZTR9UJPIA4HTxzLnQKOSGetTNeDryX425LH2cs/bbZ7x31f7hhLKr5thBl+FzgO4
 ikhQ==
X-Forwarded-Encrypted: i=1;
 AJvYcCWTIlHXwM/sEJxvGY2KNzQJm6KGtWnU8Hi8ZG/UIXQfTpBk8GxwlzxEoZX1QmAd08bdj+84lQ==@debbugs.gnu.org
X-Gm-Message-State: AOJu0YzxMZmD2VsSUrm2BmX9iYRpvyyuxypEx5G1DnH6CdNmTJ80GvaT
 Jx7kA0qlvcdNSW7IVYeL76xN1cpTL8B7Vd1Y6sP0jv8h/QXrDb+whfdKviyB
X-Gm-Gg: ASbGncsl0ldxVIMPWKI50qT5Fcnhyefx+RsrHchD9+rsfshJH8EpcqBtcV/45I6LapS
 DaI+K1zN7FKbCGAoXrz0EVs6lS8bioXi5+FaLpI/Xl0PUGNffWnH7H71PLb9zSWdbcaIeeyTMA7
 4lzhMYu9F1o16rmhH9GCqkL+4oWEIPQlWHSt8omYzUbvh8pq1xcxwV3gfYhefKvc8RJRGvDfIkz
 LpuX893OotKz+UbnbanOUciuC8JhO+AeIN13KtfXPpkbfB9AwiEF0d1/w1neEj3Gq/2eIoLH5yW
 HPnfRUSD47Z2JQQvYilY3GGeDidHxO89mYSKTU3mHUX5THptA5IfQI0=
X-Google-Smtp-Source: AGHT+IESjl+XxGXyndSjLOAVMSJUw4MsbyJtfCWh1Eg3vvdW8glCFnBtgILl/0yih9KXCVMpmFEq+Q==
X-Received: by 2002:a05:600c:1c95:b0:439:5747:7f2d with SMTP id
 5b1f17b1804b1-4396e74bf11mr53135445e9.21.1739715953065; 
 Sun, 16 Feb 2025 06:25:53 -0800 (PST)
Received: from fedora.localnet (2-230-139-124.ip202.fastwebnet.it.
 [2.230.139.124]) by smtp.gmail.com with ESMTPSA id
 5b1f17b1804b1-4395a1aa7f3sm128814865e9.29.2025.02.16.06.25.52
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Sun, 16 Feb 2025 06:25:52 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: Juri Linkov <juri@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Sun, 16 Feb 2025 15:25:51 +0100
Message-ID: <2164412.9o76ZdvQCi@fedora>
In-Reply-To: <87a5anjeuk.fsf@HIDDEN>
References: <3532547.LZWGnKmheA@fedora> <2542357.XAFRqVoOGU@fedora>
 <87a5anjeuk.fsf@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: casouri@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Ciao Juri,

In data sabato 15 febbraio 2025 18:39:55 Ora standard dell=E2=80=99Europa c=
entrale,=20
Juri Linkov ha scritto:
=2E..
>=20
> Is it time now to push your patch?

Yes!

>=20
> Delaying this will hamper the development
> since after pushing it will be possible to
> fix more issues such as adapting outline-predicate
> to `treesit-aggregated-simple-imenu-settings`, etc.

Thanks.

Vincenzo






Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 15 Feb 2025 17:49:58 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat Feb 15 12:49:58 2025
Received: from localhost ([127.0.0.1]:57782 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tjMIf-0000Vv-Qz
	for submit <at> debbugs.gnu.org; Sat, 15 Feb 2025 12:49:58 -0500
Received: from relay5-d.mail.gandi.net ([217.70.183.197]:47613)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <juri@HIDDEN>) id 1tjMId-0000Ve-R8
 for 74610 <at> debbugs.gnu.org; Sat, 15 Feb 2025 12:49:56 -0500
Received: by mail.gandi.net (Postfix) with ESMTPSA id B76874432B;
 Sat, 15 Feb 2025 17:49:47 +0000 (UTC)
From: Juri Linkov <juri@HIDDEN>
To: Vincenzo Pupillo <v.pupillo@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
In-Reply-To: <2542357.XAFRqVoOGU@fedora>
Organization: LINKOV.NET
References: <3532547.LZWGnKmheA@fedora> <5707421.ZASKD2KPVS@fedora>
 <87zfip685s.fsf@HIDDEN> <2542357.XAFRqVoOGU@fedora>
Date: Sat, 15 Feb 2025 19:39:55 +0200
Message-ID: <87a5anjeuk.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/31.0.50 (x86_64-pc-linux-gnu)
MIME-Version: 1.0
Content-Type: text/plain
X-GND-State: clean
X-GND-Score: -100
X-GND-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgdehfedukecutefuodetggdotefrodftvfcurfhrohhfihhlvgemucfitefpfffkpdcuggftfghnshhusghstghrihgsvgenuceurghilhhouhhtmecufedtudenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujfgurhephffvvefujghofhffkfgfgggtsehttdertddtredtnecuhfhrohhmpefluhhrihcunfhinhhkohhvuceojhhurhhisehlihhnkhhovhdrnhgvtheqnecuggftrfgrthhtvghrnhepffegteefveelhfeljeefueehieduiedtfffhuddtkeeffffghfevheetgeeukeehnecukfhppeeluddruddvledrleekrdehnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehinhgvthepledurdduvdelrdelkedrhedphhgvlhhopehmrghilhdrghgrnhguihdrnhgvthdpmhgrihhlfhhrohhmpehjuhhriheslhhinhhkohhvrdhnvghtpdhnsggprhgtphhtthhopeegpdhrtghpthhtohepjeegiedutdesuggvsggsuhhgshdrghhnuhdrohhrghdprhgtphhtthhopegvlhhiiiesghhnuhdrohhrghdprhgtphhtthhopegtrghsohhurhhisehgmhgrihhlrdgtohhmpdhrtghpthhtohepvhdrphhuphhilhhlohesghhmrghilhdrtghomh
X-GND-Sasl: juri@HIDDEN
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 74610
Cc: casouri@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

> Ciao Jury,
> I was just about to write, you anticipated me.
> The function `html-ts-mode--outline-predicate`, does not work correctly in 
> `mhtml-ts-mode`, the function seems to fail to calculate the end of the tag it 
> is on (see outline_issue_1.png)
>
> And, exactly as you wrote, with treesit-outline-predicate set to nil it does 
> not work because the function `treesit-outline-predicate--from-imenu` does not 
> check the variable `treesit-aggregated-simple-imenu-settings` but only 
> `treesit-simple-imenu-settings`.
>
> I attach a new patch adapted to the latest commits.

Thanks for adapting latest changes to your patch!

Is it time now to push your patch?

Delaying this will hamper the development
since after pushing it will be possible to
fix more issues such as adapting outline-predicate
to `treesit-aggregated-simple-imenu-settings`, etc.




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 14 Feb 2025 17:55:17 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Fri Feb 14 12:55:17 2025
Received: from localhost ([127.0.0.1]:51608 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tizuH-0007dJ-FY
	for submit <at> debbugs.gnu.org; Fri, 14 Feb 2025 12:55:17 -0500
Received: from mail-wr1-x436.google.com ([2a00:1450:4864:20::436]:57525)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <v.pupillo@HIDDEN>)
 id 1tizuE-0007ba-JQ
 for 74610 <at> debbugs.gnu.org; Fri, 14 Feb 2025 12:55:15 -0500
Received: by mail-wr1-x436.google.com with SMTP id
 ffacd0b85a97d-38f2b7ce319so914846f8f.2
 for <74610 <at> debbugs.gnu.org>; Fri, 14 Feb 2025 09:55:14 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1739555708; x=1740160508; darn=debbugs.gnu.org;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:from:to:cc:subject:date
 :message-id:reply-to;
 bh=thCGqTjBppA8cjzjzv3XCfv2uIL0iBtQQLNHy7D8yrw=;
 b=gWlVDdzWOKpedhbCRMO+5hGkOM1lgedTBXD8sjmmqJZIK1dm39i8ux0Edky20o8S+H
 1U5FXQcDJ0hXrv4UjDK6ApWjRESa4hOdLavwckJRDQG8uIvn2k3J5inJL4WWQOSls93a
 SzidGkc6XkozKOJ/mYzC5G6CDH/EmFMXEzyGWh+sicszI5LXCSX0evFUbP5kT8NIliSV
 40c5y3R9nZBByiiDYE+X5NitZ+3oufg2SYqCZqZ8bUKvTcjDeCCAhwKenoVqn/a3BdLf
 HfsGR6MnREnnw/NkFKDr6JC0K3Ucns19mQySovItES9SNXgllyw2B/v0DVowXyzoUqrd
 vvSA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1739555708; x=1740160508;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=thCGqTjBppA8cjzjzv3XCfv2uIL0iBtQQLNHy7D8yrw=;
 b=eGfWEa6d9I2FjmX2ZoTEUQqJF2wgQg5k0iBbgoGeO0ueIImqRgWsh1cjD1hhdVDO3F
 RIFp55GiAaXCCTmXVo1xIW+hL89RPPfmLFb9Kg1iMXxtWEliU/DtJ13DNrfHZcQ+03Ti
 S0HrwXtuY96Dmzl9F3tRe7WK/zBcOPiLwLncVeb0kF5O0wpK3204+10xm2K4uuq28xh0
 WUxdNdFZzEH90drp9sJoN8TWo9yZmqc9nRxBgfO4RBzQjRKCuUkh/eQgULq4ftgJ7F/S
 J9kv0FHkM6pEpZBY4fWCVWiEqpqLGfLBYYWtfzxxdmtaoCw02HecWuCkXoCROKHzugou
 yj+A==
X-Forwarded-Encrypted: i=1;
 AJvYcCWS33ot5dhhrRLMCNSbpR0oLz6Kz6vSHK3ad2cIQtJvlKBKQjGcPn4gqznHheCZddAP1y3/xQ==@debbugs.gnu.org
X-Gm-Message-State: AOJu0Yw+anienAqrOW3MkxAuNcwk/2vhwOXjXXJK1F6+NqvEGHXEYifd
 0X0PfVHjBNDWAzb1C+pakR3RUYtjzwJKyAYO5qyFet2tS+2JGPXX
X-Gm-Gg: ASbGncuiA6Cs13+84pHrJ/KYuomW3umYKrqzcxOuY6ye/RtddyCOI2d1yFtdlRpdLGw
 jjXit3pmUIs3BTA7tZXw/cQWNcHuUgbyPd22KoQBuoO3JwwtwhR7oVi7L3l0MzCkktfDZtSw1jp
 P9qnnqgei35j4MvAEVg8AM7AtvQ8bIm0mpWVSJg1o2xHKuOTlqRZFV0eIY8HGEz6KlLFFVN2GEh
 jdKfRO+/uVz1uXNJMmVMJvQb2/UrHFStQtAmr7WCjOkrfQI2GmVnVyDQVMh93EpM1KJNnVG4rJI
 KGozW+0RRxmSZX+ttui3mpU8lB7HHhThFoArkpUv0A47PCVaXVorvo8=
X-Google-Smtp-Source: AGHT+IG7FpcT/Y8sgqmVjzM72hOgJPShnrud80iM9Aq07MKg8UEIZWOABpAZozp58m/c6f90zT3LtQ==
X-Received: by 2002:a5d:5984:0:b0:38f:2295:c265 with SMTP id
 ffacd0b85a97d-38f2295c4a1mr11668115f8f.32.1739555707687; 
 Fri, 14 Feb 2025 09:55:07 -0800 (PST)
Received: from fedora.localnet (2-230-139-124.ip202.fastwebnet.it.
 [2.230.139.124]) by smtp.gmail.com with ESMTPSA id
 ffacd0b85a97d-38f25914171sm5251093f8f.53.2025.02.14.09.55.06
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Fri, 14 Feb 2025 09:55:07 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: Juri Linkov <juri@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Fri, 14 Feb 2025 18:55:05 +0100
Message-ID: <2542357.XAFRqVoOGU@fedora>
In-Reply-To: <87zfip685s.fsf@HIDDEN>
References: <3532547.LZWGnKmheA@fedora> <5707421.ZASKD2KPVS@fedora>
 <87zfip685s.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="nextPart3611817.dWV9SEqChM"
Content-Transfer-Encoding: 7Bit
X-Debbugs-Envelope-To: 74610
Cc: casouri@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

This is a multi-part message in MIME format.

--nextPart3611817.dWV9SEqChM
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"

Ciao Jury,
I was just about to write, you anticipated me.
The function `html-ts-mode--outline-predicate`, does not work correctly in=
=20
`mhtml-ts-mode`, the function seems to fail to calculate the end of the tag=
 it=20
is on (see outline_issue_1.png)

And, exactly as you wrote, with treesit-outline-predicate set to nil it doe=
s=20
not work because the function `treesit-outline-predicate--from-imenu` does =
not=20
check the variable `treesit-aggregated-simple-imenu-settings` but only=20
`treesit-simple-imenu-settings`.

I attach a new patch adapted to the latest commits.

Thanks.

Vincenzo.

In data gioved=C3=AC 13 febbraio 2025 18:55:59 Ora standard dell=E2=80=99Eu=
ropa centrale,=20
Juri Linkov ha scritto:
> >> I think we need now clarify the relation
> >> between mhtml-ts-mode and html-ts-mode.
> >>=20
> >> For example, currently I added a new function
> >> 'html-ts-mode--outline-predicate' to html-ts-mode.el.
> >> Should it be used in mhtml-ts-mode.el as well?
> >=20
> > I have to try but I think so, mhtml-ts-mode is declared as derived from
> > html- ts-mode (although it is not very clear to me how derived modes
> > work). The treesit-outline-predicate variable is set by html-ts-mode and
> > mhtml-ts-mode inherits the same value.
>=20
> It seems inheritance doesn't work for embedded ts-modes
> since even the primary ts-mode should be configured
> by aggregation like with treesit-aggregated-simple-imenu-settings,
> not by inheritance.
>=20
> I see that you added TODO for an aggregated version of
> treesit-defun-type-regexp.  Then treesit-outline-predicate
> also needs an aggregated version, and maybe other settings too.
> Ok, I could look how to do this with treesit-outline-predicate.
>=20
> >> What about other settings?  Should html-ts-mode and mhtml-ts-mode
> >> always be kept in sync?  Or html-ts-mode should be obsoleted
> >> when it will be superseded by mhtml-ts-mode?
> >>=20
> >> Maybe html-ts-mode would be still needed as a separate mode
> >> to be used as embedded submode in such files as e.g.
> >> Vue single-file component with *.vue files that contain parts
> >> from js-ts-mode, css-ts-mode, and html-ts-mode for templates.
> >=20
> > I don't know. In general, I think having simple major modes makes it
> > easier to build more complex aggregations, rather than having to disable
> > some features inherited from complex major modes, as in html-ts-mode: "
> >=20
> >  ;; `html-ts-mode' inherits from `html-mode' that sets
> > =20
> >   ;; regexp-based outline variables.  So need to restore
> >   ;; the default values of outline variables to be able
> >   ;; to use `treesit-outline-predicate' above.
>=20
> I checked that neither aggregation nor inheritance from html-ts-mode
> can be used for vue-ts-mode since tree-sitter-vue duplicates
> HTML grammar in its own grammar.  Even tree-sitter-astro
> incorporates a simplified HTML grammar into its own grammar.
> This is similar to HTML-like jsx elements in tsx grammar.


--nextPart3611817.dWV9SEqChM
Content-Disposition: attachment; filename="outline_issue_1.png"
Content-Transfer-Encoding: base64
Content-Type: image/png; name="outline_issue_1.png"

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
Eh4eht1u5+Chw7z25tt89sXXHssz+/iTz/H18WXsmNE8/+yT5OXns3bdem766+1cdvFFDOjf16N9
bR63JSUl/PX2u5lw+aWMGT2SMReMIsDfn6wjR1i9Zh2Ll56cJsDhcHD7XfcxbfL1jB45nEnXXUtq
WhoLFi7mw1mfMPubLzyWDbB6zTqm3HAzN0ybzMgRwwgKDCQtPZ1Zn37Oh7M+Jadsznlv1HQ/iYj8
mZSUlHgVoLvUpK2IiIiIiIiI/HeydOvV3+ux5PwNf1oZcUQHtcBqsbnLi0oLMQwHFqxYLRbnTOjl
flstFgzD4QzQSaTIUnGeWBEREREREZH/ZXGxHVi2dDE2mw2r1YbVasVqtWKxWrFYLFgsrt8AFvdI
cM6bFac+EREREREREZGzw+ue6ABFliJ2WbaQm5tNlG9zQvyc8/T6WHyxG3YMw4EDZ3huMawYFrDg
IKc4l1T7QQ7ZknDgMC9WRERERERERERERERERETknFCjEB3AQSmHfPZxtDSDiJymhNkaEOQbiq/N
F8NiwTAMSinFXlpCYUkeOaXHyPBJI8+WbV6UiIiIiIiIiIiIiIiIiIjIOaXGIbpLvjWXZL/d+Bi+
BBeH4lPqiw1frEAJJZTY7OTZcrDbis13FREREREREflT8vexYrO5fsoN6W5xDt9u/nEp/7eIiIiI
iIiInF2nHaK7lFjsnPA5WgtLEhERERERERERERERERER+WNZzQUiIiIiIiIiIiIiIiIiIiJ/VgrR
RUREREREREREREREREREyihEFxERERERERERERERERERKaMQXUREREREREREREREREREpIxCdBER
ERERERERERERERERkTIK0UVERERERERERERERERERMooRBcRERERERERERERERERESnj42MUm8tE
RERERERERERERERERET+lNQTXUREREREREREREREREREpIxCdBERERERERERERERERERkTIK0UVE
RERERERERERERERERMooRBcRERERERERERERERERESmjEF1ERERERERERERERERERKSMQnQRERER
EREREREREREREZEyCtFFRERERERERERERERERETKKEQXEREREREREREREREREREpoxBdRERERERE
RERERERERESkjI+5oDLx8XF0797dXExxcTFfffU13bt3IyQklBUrVgDQv39/goODWbBggfkuZ011
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
ERERERERERERERERESmjEF1ERERERERERERERERERKSMj91uN5eJiIiIiIiIiIiIiIiIiIj8Kfn4
+vqay0RERERERERERERERERERP6UNJy7iIiIiIiIiIiIiIiIiIhIGYXoIiIiIiIiIiIiIiIiIiIi
5ozrNQAAIABJREFUZRSii4iIiIiIiIiIiIiIiIiIlFGILiIitcZut5uL5BS0v+R/iY5nERERERER
ERER+V+hEF1ERERERERERERERERERKSMQnQREREREREREZH/Z+9Om+S6zgNBv1lVKCwFEEthLUKk
uIACF4gkRIqyaY8tyW7bbTuiHdM9HdPTMaGYf0F9UEzog/g39LknOron7OmWJZlabK12gJIpbiJB
UiRI7CD2AmrL+cC6yaxT9968WXmzKrPyeSIygHvue06eu2RWnnzzngsAALBMEh0AAAAAAAAAlkmi
AwAAAAAAAMAySXQAAAAAAAAAWCaJDgAAAAAAAADLJNEBAAAAAAAAYJkkOgAAAAAAAAAsk0QHAAAA
AAAAgGWS6AAAAAAAAACwbCIt6MZjjz0aJ0+ejFdffTVefvlX6er4kz/5aty6dSt+9rOfp6v6KutX
am5uLv7Lf/l/4uTJp2Pnzl3x4x//OCIinn/++Ziamorvfve7aZV1d/To0Th+/HOxZ8/e2LJlIm7d
uh3vv/9+vP7663H37t00fFMYpm1Oz62FhYW4fft2fPDBmXj99dfjzp07K+LTc43u7N+/Px599Hjs
338gtm3bGnNzc3HlypX4zW9ejYsXLw7Ua7dXjUYj9u3bF7t27YotW7bE4uJizM/Px8cffxw3b95M
w0fCM898IY4ePRr//b//v+mq2Lt3b/zlX/7b+O53vxcXLlxIV6/Jf/gP/z5ef/2N+M1vfpOuGirb
t2+PPXv2xPbt22NsbCyWlpbi7t27ceXKlZidnU3DB8aw9ruq9r8fzWaz9fp+7bXX4sMPP0rDR97Y
2Fg8/vjj8eCDD8TU1FTcvXs3rl+/Hm+++dt4//330/ANsZn+BgEAAAAAK/WURM889thj8d5778XH
H19NV22YZrMZP/zhj1aULS0tRUTE7OydaDQG7yL8p556Kp544vE4c+ZMvPzyqZifX4h77rknHnnk
WNx//33xve99P27fvp1WG2rDuM3NZjNeeumliIgYH5+I6enpOHbs4XjooQfjpZd+EFeuXGnFrue5
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
NjYWW7ZsaSV4O72HFdno97a1On/+fJw/fz52794dX/nKV+L3f//34jvf+fvK29Npf2bSv39V/+4W
vef04c8jAAAAALCOak2iN5vN+OUvfxn/5t/8aZw4cWLFl913737yJeSrr74W7733XlutT9y48ckX
1+fOnYtnn30mduzYEXv27Imf/OSn0Vy+2nhmZiYajbGYm5urNO3wWq6SrdrPup0/fyHuvXcmJiYm
cpMER48ejWazGefOfTIFcyxPIZqamtrR+hK9m21ZWlr/b3s32zbPzMzE+Pj4qqnBO6nS50bjkwRS
1SsOM3fv3o1ms5l7Rdz27dtLEy7tzp07Hzdv3ozPfvb+OHfuXOzYsaN1n/p+OHfufHzmM58kt6r2
sZttXVxcjNdffyPeeOPNuO++z8QzzzwTO3ZMxQ9+8INKx2M9zM3NxbVr12Lfvn2thPF9990XCwsL
cfHixZifn49msxmHDx9eUe/mzZtx6NCh2LlzZywsLMT4+HgrMZhNg/zxxx/nTllddV+vl7t378b2
7duj0WisSpZnP8BJk9ydXtfZfczT11Kj0Vj1I5uq59OguH37duzcubP1A4putSeY11OVfmfnbl6i
d2JiYlW9vG3ZsmXLiqnAb9++3Uqe3759OyYmJvqeRI8On03Gx8fiz/7s38Ts7GycOvVy3Lx5MxYW
FuIP/uD5FXFl72FFBuW9ba2uXbsWp0+/HSdOnIhGo1Fpe6ruzzzZazx9r0h1es8BAAAAAIZT7Zcd
Xrx4Md5555147LFHV0yTuri4FJcuXYq9e/fG1atXVz0WFz/5AvzcuXMxMTERjzzySNy6dat1r9IP
P/woDh06FAcPHoxz586vSqjUpWo/6/bWW2/F5ORkPPbYo+mq2LZtWxw//rk4c+bMiitOd+7cueJK
7rGxsThw4GDrBwYbtS1VbaZt3rlzKp599pm4evVqfPRR+a0GUlX6vLCwEFevXo3PfOZoWr1lcXEx
JiZW/i5mcXExLl68FEePrqy3ffv22L9/Os6fL07mtGs2m/HOO+/EfffdF5/5zH3x8ccfx7Vr19Kw
2rz99tsxMTERTz/9VLqq0Fq2tdlsxu9+93789rdvxb59eyMqHo/1Mj4+HktLS9FsNmPr1q0xMTER
ly5daiX/8q7gXVpaips3b8auXbti586dMTs720ocN5dngNi6dWvcvXt31aNf76trde7c+RgbG4sH
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
fO6Rnbm9vT2XSgjhasr59+zOYHn95tOk5bhMbR8qU/t/ePvqXl1Ne1zi+KnLcRlqn7ucf/vlXt2a
7fFyXOL2eDkucXu8HJe4vVuO68eWS/vNVYb2p7g9Xo5L3B4vx2Xp9ng5LlPbW5eh/eXa24fKUP+4
PV6OS9weL8dl6fZ4OS5D7UNlqP+1t7cuQ+tv3R6X1vFxGeo/1F5bascbih9qj8tQfOv22rLEeP26
VPtcZew5kPJ7ic+H4/Ls2bMvyk8//alY7sX/+S/n77///vzDDz+cf/zxx/PTp0/PX3/99fmbb745
f/vtt+cnT56cnzx5cv7uu+8+l3iOiqLsq7Q8xiuKoijXWfzboSiKso1ymCvR/QqES3W/UEr9IilX
fzTXvB2W/EU3bN3ax8Op46d+bRqbug5Yytr7c8uxj2CJY2Znrqt8AQCAZSx1rgBA2WGS6Nfwx6Mt
3Tou3N3WmfUt+UfYnLXXz3FtYf9nP6buK/Y3LjEm2X2Jre6LrV7vNVn6WLL3cyAAAACANRwmiR4a
J9JbJ7hLzz5vve6U0nOR50iuzzHGnFJXX8bLU/iDM1Ns7fPCvtQef1ofD2vVzh+WVPpsLL3vdp/d
nNw8a7R+vXOMsbbSNmqh1bkPAAAAwLU7VBI9NEyk5xLccygl0NdQSqDPpWb83x/zWDb3+z23Jf+Y
yvWp+by0dg2fxy0ZkzCaevyY2n9te58/12vpZOkYpflMne/U/mPUjL/E8bPWEtuor8U5DwD1ljz2
AwAA8zlcEj00TKSzjqXex+4Krv5/gS8t9Xk8iq0eZxwPj8F7y55tcd9dck7OdZbzz3/+Mzx48CCu
DmHkjwuZrnY7+2wAAAAwxk1ccRRdIh2gxK3SYTvGXFkKLMPn8Utb2h4S6Mv67//+77jqs//+7/8b
V9HI6XQaVQAAAGCswybRw4yJ9BfPHxafS15qG2No/KU9evqyaWJxaPxSW2strrrsxswptU2Vei2l
+aTi9660v6UeXVAb31ppPmHlz8selfb/Paid/1B8qW3p42FqXaX4MDD/LRiaP/u2tfe3di6l+V/y
eZxqaPxS2xaU5p/anpeSQF/ef/7nf4b/+Z//iavDP//5z/Du3bu4Gq5afJyLl6eKx4uXAQAA5nTI
27n3dYn0qX9sKiW6h55nnuob90nFrGkocTdVafylk5SX6p/Qd/+f+wNp6Q+rcZ9UbH85jg+FPy6k
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
WQAAAABJRU5ErkJggg==


--nextPart3611817.dWV9SEqChM
Content-Disposition: attachment; filename="0001-Add-mhtml-ts-mode.patch"
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-patch; charset="UTF-8";
 name="0001-Add-mhtml-ts-mode.patch"

=46rom 12219f271ca252037babe80ddd7f52e5a89877fb Mon Sep 17 00:00:00 2001
=46rom: Vincenzo Pupillo <v.pupillo@HIDDEN>
Date: Fri, 14 Feb 2025 18:38:51 +0100
Subject: [PATCH] Add mhtml-ts-mode.

New major-mode alternative to mhtml-mode, based on treesitter, for
editing files containing html, javascript and css.

* etc/NEWS: Mention the new mode and new functions.
* lisp/textmodes/mhtml-ts-mode.el: New file.
* lisp/progmodes/js.el
(js--treesit-thing-settings): New variable.
(js--treesit-font-lock-feature-list); New variable.
(js--treesit-simple-imenu-settings): New variable.
(js--treesit-defun-type-regexp): New variable.
(js--treesit-jsdoc-comment-regexp): New variable.
(js-ts-mode): Use of new variables instead of direct assignment of
values.
* lisp/textmodes/css-mode.el
(css-mode--menu): New variable.
(css-mode-map): Use new variable.
(css--treesit-font-lock-feature-list): New variable.
(css--treesit-simple-imenu-settings): New variable.
(css--treesit-defun-type-regexp): New variable.
(cs-ts-mode): Use of new variables instead of direct assignment of
values.
* lisp/textmodes/html-ts-mode.el
(html-ts-mode--treesit-things-settings): New variable.
(html-ts-mode--treesit-font-lock-feature-list): New variable.
(html-ts-mode--treesit-simple-imenu-settings): New variable.
(html-ts-mode--treesit-defun-type-regexp): New variable.
(html-ts-mode): Use of new variables instead of direct assignment of
values.
* lisp/treesit.el
(treesit-merge-font-lock-feature-list): New fuction.
(treesit-replace-font-lock-feature-settings): New fuction.
(treesit-modify-indent-rules): New function.
=2D--
 etc/NEWS                        |  29 ++
 lisp/progmodes/js.el            |  72 ++--
 lisp/textmodes/css-mode.el      |  47 ++-
 lisp/textmodes/html-ts-mode.el  |  54 +--
 lisp/textmodes/mhtml-ts-mode.el | 594 ++++++++++++++++++++++++++++++++
 lisp/treesit.el                 |  68 ++++
 6 files changed, 801 insertions(+), 63 deletions(-)
 create mode 100644 lisp/textmodes/mhtml-ts-mode.el

diff --git a/etc/NEWS b/etc/NEWS
index 31109f0857c..84e970cdc63 100644
=2D-- a/etc/NEWS
+++ b/etc/NEWS
@@ -1204,6 +1204,12 @@ runs its body, and removes the current buffer from
=20
 =0C
 * New Modes and Packages in Emacs 31.1
+** New major modes based on the tree-sitter library
+
+*** New major mode 'mhtml-ts-mode'.
+An optional major mode based on the tree-sitter library for editing html
+files. This mode handles indentation, fontification, and commenting for
+embedded JavaScript and CSS.
=20
 =0C
 * Incompatible Lisp Changes in Emacs 31.1
@@ -1351,6 +1357,29 @@ language symbol.  For example, 'cpp' is translated t=
o "C++".  A new
 variable 'treesit-language-display-name-alist' holds the translations of
 language symbols where that translation is not trivial.
=20
++++
+++++
+*** New function 'treesit-merge-font-lock-feature-list'.
+This function the merge two tree-sitter font lock feature lists.
+Returns a new font lock feature list with no duplicates in the same level.
+It can be used to merge font lock feature lists in a multi-language major =
mode.
+
++++
+*** New function 'treesit-replace-font-lock-feature-settings'.
+Given two treesit-font-lock-settings replaces the feature in the second
+font-lock-settings with the same feature in the first
+font-lock-settings. In a multi-linguage major mode it is sometimes
+necessary to replace features from one of the major modes, with others
+that are better suited to the new multilingual context.
+
++++
+*** New function 'treesit-modify-indent-rules'.
+Given two treesit ident rules, it replaces, adds, or prepends the new
+rules to the old ones, then returns a new treesit indent rules.
+In a multi-linguage major mode it is sometimes necessary to modify rules
+from one of the major modes, with others that are better suited to the
+new multilingual context.
+
 +++
 *** New command 'treesit-explore'.
 This command replaces 'treesit-explore-mode'.  It turns on
diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
index 3168395acf1..f2acf9f40d6 100644
=2D-- a/lisp/progmodes/js.el
+++ b/lisp/progmodes/js.el
@@ -3920,6 +3920,44 @@ js--treesit-list-nodes
 (defvar js--treesit-jsdoc-beginning-regexp (rx bos "/**")
   "Regular expression matching the beginning of a jsdoc block comment.")
=20
+(defvar js--treesit-thing-settings
+  `((javascript
+     (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
+     (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
+     (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
+     (text ,(js--regexp-opt-symbol '("comment"
+                                     "string_fragment")))))
+  "Settings for `treesit-thing-settings'.")
+
+(defvar js--treesit-font-lock-feature-list
+  '(( comment document definition)
+    ( keyword string)
+    ( assignment constant escape-sequence jsx number
+      pattern string-interpolation)
+    ( bracket delimiter function operator property))
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar js--treesit-simple-imenu-settings
+  `(("Function" "\\`function_declaration\\'" nil nil)
+    ("Variable" "\\`lexical_declaration\\'"
+     js--treesit-valid-imenu-entry nil)
+    ("Class" ,(rx bos (or "class_declaration"
+                          "method_definition")
+                  eos)
+     nil nil))
+  "Settings for `treesit-simple-imenu'.")
+
+(defvar js--treesit-defun-type-regexp
+  (rx (or "class_declaration"
+          "method_definition"
+          "function_declaration"
+          "lexical_declaration"))
+  "Settings for `treesit-defun-type-regexp'.")
+
+(defvar js--treesit-jsdoc-comment-regexp
+  (rx (or "comment" "line_comment" "block_comment" "description"))
+  "Regexp for `c-ts-common--comment-regexp'.")
+
 ;;;###autoload
 (define-derived-mode js-ts-mode js-base-mode "JavaScript"
   "Major mode for editing JavaScript.
@@ -3951,29 +3989,15 @@ js-ts-mode
     ;; Indent.
     (setq-local treesit-simple-indent-rules js--treesit-indent-rules)
     ;; Navigation.
=2D    (setq-local treesit-defun-type-regexp
=2D                (rx (or "class_declaration"
=2D                        "method_definition"
=2D                        "function_declaration"
=2D                        "lexical_declaration")))
+    (setq-local treesit-defun-type-regexp js--treesit-defun-type-regexp)
+
     (setq-local treesit-defun-name-function #'js--treesit-defun-name)
=20
=2D    (setq-local treesit-thing-settings
=2D                `((javascript
=2D                   (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
=2D                   (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
=2D                   (sentence ,(js--regexp-opt-symbol js--treesit-sentenc=
e-nodes))
=2D                   (text ,(js--regexp-opt-symbol '("comment"
=2D                                                   "string_fragment"))))=
))
+    (setq-local treesit-thing-settings js--treesit-thing-settings)
=20
     ;; Fontification.
     (setq-local treesit-font-lock-settings js--treesit-font-lock-settings)
=2D    (setq-local treesit-font-lock-feature-list
=2D                '(( comment document definition)
=2D                  ( keyword string)
=2D                  ( assignment constant escape-sequence jsx number
=2D                    pattern string-interpolation)
=2D                  ( bracket delimiter function operator property)))
+    (setq-local treesit-font-lock-feature-list js--treesit-font-lock-featu=
re-list)
=20
     (when (treesit-ready-p 'jsdoc t)
       (setq-local treesit-range-settings
@@ -3983,17 +4007,11 @@ js-ts-mode
                    :local t
                    `(((comment) @capture (:match ,js--treesit-jsdoc-beginn=
ing-regexp @capture)))))
=20
=2D      (setq c-ts-common--comment-regexp (rx (or "comment" "line_comment"=
 "block_comment" "description"))))
+      (setq c-ts-common--comment-regexp js--treesit-jsdoc-comment-regexp))
=20
     ;; Imenu
=2D    (setq-local treesit-simple-imenu-settings
=2D                `(("Function" "\\`function_declaration\\'" nil nil)
=2D                  ("Variable" "\\`lexical_declaration\\'"
=2D                   js--treesit-valid-imenu-entry nil)
=2D                  ("Class" ,(rx bos (or "class_declaration"
=2D                                        "method_definition")
=2D                                eos)
=2D                   nil nil)))
+    (setq-local treesit-simple-imenu-settings js--treesit-simple-imenu-set=
tings)
+
     (treesit-major-mode-setup)
=20
     (add-to-list 'auto-mode-alist
diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index 53340195386..35c61e4f66d 100644
=2D-- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -893,13 +893,7 @@ css-mode-syntax-table
     (modify-syntax-entry ?? "." st)
     st))
=20
=2D(defvar-keymap css-mode-map
=2D  :doc "Keymap used in `css-mode'."
=2D  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
=2D  ;; `info-complete-symbol' is not used.
=2D  "<remap> <complete-symbol>" #'completion-at-point
=2D  "C-c C-f" #'css-cycle-color-format
=2D  :menu
+(defvar css-mode--menu
   '("CSS"
     :help "CSS-specific features"
     ["Reformat block" fill-paragraph
@@ -910,7 +904,17 @@ css-mode-map
     ["Describe symbol" css-lookup-symbol
      :help "Display documentation for a CSS symbol"]
     ["Complete symbol" completion-at-point
=2D     :help "Complete symbol before point"]))
+     :help "Complete symbol before point"])
+    "Menu bar for `css-mode'")
+
+(defvar-keymap css-mode-map
+  :doc "Keymap used in `css-mode'."
+  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
+  ;; `info-complete-symbol' is not used.
+  "<remap> <complete-symbol>" #'completion-at-point
+  "C-c C-f" #'css-cycle-color-format
+  :menu
+  css-mode--menu)
=20
 (eval-and-compile
   (defconst css--uri-re
@@ -1771,6 +1775,21 @@ css--extract-index-name
               (replace-regexp-in-string "[\n ]+" " " s)))
            res)))))))
=20
+(defvar css--treesit-font-lock-feature-list
+  '((selector comment query keyword)
+    (property constant string)
+    (error variable function operator bracket))
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar css--treesit-simple-imenu-settings
+  `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
+      nil nil))
+  "Settings for `treesit-simple-imenu'.")
+
+(defvar css--treesit-defun-type-regexp
+  "rule_set"
+  "Settings for `treesit-defun-type-regexp'.")
+
 (define-derived-mode css-base-mode prog-mode "CSS"
   "Generic mode to edit Cascading Style Sheets (CSS).
=20
@@ -1825,16 +1844,12 @@ css-ts-mode
     ;; Tree-sitter specific setup.
     (setq treesit-primary-parser (treesit-parser-create 'css))
     (setq-local treesit-simple-indent-rules css--treesit-indent-rules)
=2D    (setq-local treesit-defun-type-regexp "rule_set")
+    (setq-local treesit-defun-type-regexp css--treesit-defun-type-regexp)
     (setq-local treesit-defun-name-function #'css--treesit-defun-name)
     (setq-local treesit-font-lock-settings css--treesit-settings)
=2D    (setq-local treesit-font-lock-feature-list
=2D                '((selector comment query keyword)
=2D                  (property constant string)
=2D                  (error variable function operator bracket)))
=2D    (setq-local treesit-simple-imenu-settings
=2D                `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
=2D                    nil nil)))
+    (setq-local treesit-font-lock-feature-list css--treesit-font-lock-feat=
ure-list)
+    (setq-local treesit-simple-imenu-settings css--treesit-simple-imenu-se=
ttings)
+
     (treesit-major-mode-setup)
=20
     (add-to-list 'auto-mode-alist '("\\.css\\'" . css-ts-mode))))
diff --git a/lisp/textmodes/html-ts-mode.el b/lisp/textmodes/html-ts-mode.el
index 7e6c3e0b7d1..31f72cb0fcd 100644
=2D-- a/lisp/textmodes/html-ts-mode.el
+++ b/lisp/textmodes/html-ts-mode.el
@@ -87,6 +87,35 @@ html-ts-mode--font-lock-settings
    `((attribute_name) @font-lock-variable-name-face))
   "Tree-sitter font-lock settings for `html-ts-mode'.")
=20
+(defvar html-ts-mode--treesit-things-settings
+  `((html
+     (sexp ,(regexp-opt '("element"
+                          "text"
+                          "attribute"
+                          "value")))
+     (list ,(rx (or
+                 ;; Also match script_element and style_element
+                 "element"
+                 ;; HTML comments have the element syntax
+                 "comment")))
+     (sentence ,(rx (and bos (or "tag_name" "attribute") eos)))
+     (text ,(regexp-opt '("comment" "text")))))
+  "Settings for `treesit-thing-settings'.")
+
+(defvar html-ts-mode--treesit-font-lock-feature-list
+  '((comment keyword definition)
+    (property string)
+    () ())
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar html-ts-mode--treesit-simple-imenu-settings
+  '((nil "element" nil nil))
+  "Settings for `treesit-simple-imenu'.")
+
+(defvar html-ts-mode--treesit-defun-type-regexp
+  "element"
+  "Settings for `treesit-defun-type-regexp'.")
+
 (defun html-ts-mode--defun-name (node)
   "Return the defun name of NODE.
 Return nil if there is no name or if NODE is not a defun node."
@@ -119,33 +148,18 @@ html-ts-mode
   (setq-local treesit-simple-indent-rules html-ts-mode--indent-rules)
=20
   ;; Navigation.
=2D  (setq-local treesit-defun-type-regexp "element")
+  (setq-local treesit-defun-type-regexp html-ts-mode--treesit-defun-type-r=
egexp)
+
   (setq-local treesit-defun-name-function #'html-ts-mode--defun-name)
=20
=2D  (setq-local treesit-thing-settings
=2D              `((html
=2D                 (sexp ,(regexp-opt '("element"
=2D                                      "text"
=2D                                      "attribute"
=2D                                      "value")))
=2D                 (list ,(rx (or
=2D                             ;; Also match script_element and style_elem=
ent
=2D                             "element"
=2D                             ;; HTML comments have the element syntax
=2D                             "comment")))
=2D                 (sentence ,(rx (and bos (or "tag_name" "attribute") eos=
)))
=2D                 (text ,(regexp-opt '("comment" "text"))))))
+  (setq-local treesit-thing-settings html-ts-mode--treesit-things-settings)
=20
   ;; Font-lock.
   (setq-local treesit-font-lock-settings html-ts-mode--font-lock-settings)
=2D  (setq-local treesit-font-lock-feature-list
=2D              '((comment keyword definition)
=2D                (property string)
=2D                () ()))
+  (setq-local treesit-font-lock-feature-list html-ts-mode--treesit-font-lo=
ck-feature-list)
=20
   ;; Imenu.
=2D  (setq-local treesit-simple-imenu-settings
=2D              '((nil "element" nil nil)))
+  (setq-local treesit-simple-imenu-settings html-ts-mode--treesit-simple-i=
menu-settings)
=20
   ;; Outline minor mode.
   (setq-local treesit-outline-predicate #'html-ts-mode--outline-predicate)
diff --git a/lisp/textmodes/mhtml-ts-mode.el b/lisp/textmodes/mhtml-ts-mode=
=2Eel
new file mode 100644
index 00000000000..9be1a14c257
=2D-- /dev/null
+++ b/lisp/textmodes/mhtml-ts-mode.el
@@ -0,0 +1,594 @@
+;;; mhtml-ts-mode.el --- Major mode for HTML using tree-sitter -*- lexical=
=2Dbinding: t; -*-
+
+;; Copyright (C) 2024 Free Software Foundation, Inc.
+
+;; Author: Vincenzo Pupillo <v.pupillo@HIDDEN>
+;; Maintainer: Vincenzo Pupillo <v.pupillo@HIDDEN>
+;; Created: Nov 2024
+;; Keywords: HTML languages hypermedia tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; This package provides `mhtml-ts-mode' which is a major mode
+;; for editing HTML files with embedded JavaScript and CSS.
+;; Tree Sitter is used to parse each of these languages.
+;;
+;; Please note that this package requires `html-ts-mode', which
+;; registers itself as the major mode for editing HTML.
+;;
+;; This package is compatible and has been tested with the following
+;; tree-sitter grammars:
+;; * https://github.com/tree-sitter/tree-sitter-html
+;; * https://github.com/tree-sitter/tree-sitter-javascript
+;; * https://github.com/tree-sitter/tree-sitter-jsdoc
+;; * https://github.com/tree-sitter/tree-sitter-css
+;;
+;; Features
+;;
+;; * Indent
+;; * Flymake
+;; * IMenu
+;; * Navigation
+;; * Which-function
+;; * Tree-sitter parser installation helper
+
+;;; Code:
+
+(require 'treesit)
+(require 'html-ts-mode)
+(require 'css-mode) ;; for embed css into html
+(require 'js) ;; for embed javascript into html
+
+(eval-when-compile
+  (require 'rx))
+
+;; This tells the byte-compiler where the functions are defined.
+;; Is only needed when a file needs to be able to byte-compile
+;; in a Emacs not built with tree-sitter library.
+(treesit-declare-unavailable-functions)
+
+;; In a multi-language major mode can be useful to have an "installer" to
+;; simplify the installation of the grammars supported by the major-mode.
+(defvar mhtml-ts-mode--language-source-alist
+  '((html . ("https://github.com/tree-sitter/tree-sitter-html"  "v0.23.2"))
+    (javascript . ("https://github.com/tree-sitter/tree-sitter-javascript"=
 "v0.23.1"))
+    (jsdoc . ("https://github.com/tree-sitter/tree-sitter-jsdoc" "v0.23.2"=
))
+    (css . ("https://github.com/tree-sitter/tree-sitter-css" "v0.23.1")))
+  "Treesitter language parsers required by `mhtml-ts-mode'.
+You can customize this variable if you want to stick to a specific
+commit and/or use different parsers.")
+
+(defun mhtml-ts-mode-install-parsers ()
+  "Install all the required treesitter parsers.
+`mhtml-ts-mode--language-source-alist' defines which parsers to install."
+  (interactive)
+  (let ((treesit-language-source-alist mhtml-ts-mode--language-source-alis=
t))
+    (dolist (item mhtml-ts-mode--language-source-alist)
+      (treesit-install-language-grammar (car item)))))
+
+;;; Custom variables
+
+(defgroup mhtml-ts-mode nil
+  "Major mode for editing HTML files, based on `html-ts-mode'.
+Works with JS and CSS and for that use `js-ts-mode' and `css-ts-mode'."
+  :prefix "mhtml-ts-mode-"
+  ;; :group 'languages
+  :group 'html)
+
+(defcustom mhtml-ts-mode-js-css-indent-offset 2
+  "JavaScript and CSS indent spaces related to the <script> and <style> HT=
ML tags.
+By default should have same value as `html-ts-mode-indent-offset'."
+  :tag "HTML javascript or css indent offset"
+  :version "31.1"
+  :type 'integer
+  :safe 'integerp)
+
+(defcustom mhtml-ts-mode-pretty-print-command
+  ;; prefer tidy because it's used by sgml-mode
+  (let ((executable nil))
+    (cond ((setq executable (executable-find "tidy"))
+           (format
+            "%s --gnu-emacs yes --wrap 0 --indent-spaces %s -q -i -"
+            executable html-ts-mode-indent-offset))
+          ((setq executable (executable-find "xmllint"))
+           (format "%s --html --quiet --format -" executable))
+          (t "Install tidy, ore some other HTML pretty print tool, and set=
 `mhtml-ts-mode-pretty-print-command'.")))
+  "The command to pretty print the current HTML buffer."
+  :type 'string
+  :version "31.1")
+
+(defvar mhtml-ts-mode--js-css-indent-offset
+  mhtml-ts-mode-js-css-indent-offset
+  "Internal copy of `mhtml-ts-mode-js-css-indent-offset'.
+The value changes, by `mhtml-ts-mode--tag-relative-indent-offset' accordin=
g to
+the value of `mhtml-ts-mode-tag-relative-indent'.")
+
+(defun mhtml-ts-mode--tag-relative-indent-offset (sym val)
+  "Custom setter for `mhtml-ts-mode-tag-relative-indent'.
+Apart from setting the default value of SYM to VAL, also change the
+value of SYM in `mhtml-ts-mode' buffers to VAL.  SYM should be
+`mhtml-ts-mode-tag-relative-indent', and VAL should be t, nil or
+`ignore'.  When sym is `mhtml-ts-mode-tag-relative-indent' set the
+value of `mhtml-ts-mode--js-css-indent-offset' to 0 if VAL is t,
+otherwise to `mhtml-ts-mode-js-css-indent-offset'."
+  (set-default sym val)
+  (when (eq sym 'mhtml-ts-mode-tag-relative-indent)
+    (setq
+     mhtml-ts-mode--js-css-indent-offset
+     (if (eq val t)
+         mhtml-ts-mode-js-css-indent-offset
+       0))))
+
+(defcustom mhtml-ts-mode-tag-relative-indent t
+  "How <script> and <style> bodies are indented relative to the tag.
+
+When t, indentation looks like:
+
+  <script>
+    code();
+  </script>
+
+When nil, indentation of the tag body starts just below the
+tag, like:
+
+  <script>
+  code();
+  </script>
+
+When `ignore', the tag body starts in the first column, like:
+
+  <script>
+code();
+  </script>"
+  :type '(choice (const nil) (const t) (const ignore))
+  :safe 'symbolp
+  :set #'mhtml-ts-mode--tag-relative-indent-offset
+  :version "31.1")
+
+(defcustom mhtml-ts-mode-css-fontify-colors t
+  "Whether CSS colors should be fontified using the color as the backgroun=
d.
+If non-nil, text representing a CSS color will be fontified
+such that its background is the color itself.
+Works like `css--fontify-region'."
+  :tag "HTML colors the CSS properties values."
+  :version "31.1"
+  :type 'boolean
+  :safe 'booleanp)
+
+(defvar mhtml-ts-mode-saved-pretty-print-command nil
+  "The command last used to pretty print in this buffer.")
+
+(defun mhtml-ts-mode-pretty-print (command)
+  "Prettify the current buffer.
+Argument COMMAND The command to use."
+  (interactive
+   (list (read-string
+          "Prettify command: "
+          (or mhtml-ts-mode-saved-pretty-print-command
+              (concat mhtml-ts-mode-pretty-print-command " ")))))
+  (setq mhtml-ts-mode-saved-pretty-print-command command)
+  (save-excursion
+    (shell-command-on-region
+     (point-min) (point-max)
+     command (buffer-name) t
+     "*mhtml-ts-mode-pretty-pretty-print-errors*" t)))
+
+(defun mhtml-ts-mode--switch-fill-defun (&rest arguments)
+  "Switch between `fill-paragraph' and `prog-fill-reindent-defun'.
+In an HTML region it calls `fill-paragraph' as does `html-ts-mode',
+otherwise it calls `prog-fill-reindent-defun'.
+Optional ARGUMENTS to to be passed to it."
+  (interactive)
+  (if (eq (treesit-language-at (point)) 'html)
+      (funcall-interactively #'fill-paragraph arguments)
+    (funcall-interactively #'prog-fill-reindent-defun arguments)))
+
+(defvar-keymap mhtml-ts-mode-map
+  :doc "Keymap for `mhtml-ts-mode' buffers."
+  :parent html-mode-map
+  ;; `mhtml-ts-mode' derive from `html-ts-mode' so the keymap is the
+  ;; same, we need to add some mapping from others languages.
+  "C-c C-f" #'css-cycle-color-format
+  "M-q" #'mhtml-ts-mode--switch-fill-defun)
+
+;; Place the CSS menu in the menu bar as well.
+(easy-menu-define mhtml-ts-mode-menu mhtml-ts-mode-map
+  "Menu bar for `mhtml-ts-mode'."
+  css-mode--menu)
+
+;; To enable some basic treesiter functionality, you should define
+;; a function that recognizes which grammar is used at-point.
+;; This function should be assigned to `treesit-language-at-point-function'
+(defun mhtml-ts-mode--language-at-point (point)
+  "Return the language at POINT assuming the point is within a HTML buffer=
=2E"
+  (let* ((node (treesit-node-at point 'html))
+         (parent (treesit-node-parent node))
+         (node-query (format "(%s (%s))"
+                             (treesit-node-type parent)
+                             (treesit-node-type node))))
+    (cond
+     ((equal "(script_element (raw_text))" node-query) (js--treesit-langua=
ge-at-point point))
+     ((equal "(style_element (raw_text))" node-query) 'css)
+     (t 'html))))
+
+;; Custom font-lock function that's used to apply color to css color
+;; The signature of the function should be conforming to signature
+;; QUERY-SPEC required by `treesit-font-lock-rules'.
+(defun mhtml-ts-mode--colorize-css-value (node override start end &rest _)
+  "Colorize CSS property value like `css--fontify-region'.
+For NODE, OVERRIDE, START, and END, see `treesit-font-lock-rules'."
+  (if (and mhtml-ts-mode-css-fontify-colors
+           (string-equal "plain_value" (treesit-node-type node)))
+      (let ((color (css--compute-color start (treesit-node-text node t))))
+        (when color
+          (with-silent-modifications
+            (add-text-properties
+             (treesit-node-start node) (treesit-node-end node)
+             (list 'face (list :background color
+                               :foreground (readable-foreground-color
+                                            color)
+                               :box '(:line-width -1)))))))
+    (treesit-fontify-with-override
+     (treesit-node-start node) (treesit-node-end node)
+     'font-lock-variable-name-face
+     override start end)))
+
+;; Embedded languages =E2=80=8B=E2=80=8Bshould be indented according to th=
e language
+;; that embeds them.
+;; This function signature complies with `treesit-simple-indent-rules'
+;; ANCHOR.
+(defun mhtml-ts-mode--js-css-tag-bol (_node _parent &rest _)
+  "Find the first non-space characters of html tags <script> or <style>.
+Return `line-beginning-position' when `treesit-node-at' is html, or
+`mhtml-ts-mode-tag-relative-indent' is equal to ignore.
+NODE and PARENT are ignored."
+  (if (or (eq (treesit-language-at (point)) 'html)
+          (eq mhtml-ts-mode-tag-relative-indent 'ignore))
+      (line-beginning-position)
+    ;; Ok, we are in js or css block.
+    (save-excursion
+      (re-search-backward "<script.*>\\|<style.*>" nil t))))
+
+;; Treesit supports 4 level of decoration, `treesit-font-lock-level'
+;; define which level to use.  Major modes categorize their fontification
+;; features, these categories are defined by `treesit-font-lock-rules' of
+;; each major-mode using :feature keyword.
+;; In a multiple language Major mode it's a good idea to provide, for each
+;; level, the union of the :feature of the same level.
+;; TODO: Since the feature-list is not defined per "parser" (like, for
+;; example, the thing-settings), the same feature can appear in
+;; different levels, so the appearance of a multiple main mode can be
+;; different from the main mode used.  For e.g the feature "function" is
+;; at level 4 for Javascript while it is at level 3 for CSS.
+(defvar mhtml-ts-mode--treesit-font-lock-feature-list
+  (treesit-merge-font-lock-feature-list
+   html-ts-mode--treesit-font-lock-feature-list
+   (treesit-merge-font-lock-feature-list
+    js--treesit-font-lock-feature-list
+    css--treesit-font-lock-feature-list))
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar mhtml-ts-mode--treesit-font-lock-settings
+  (append html-ts-mode--font-lock-settings
+          js--treesit-font-lock-settings
+          ;; Let's replace a css rule with a new one that adds color to
+          ;; the css value.
+          (treesit-replace-font-lock-feature-settings
+           (treesit-font-lock-rules
+            :language 'css
+            :override t
+            :feature 'variable
+            '((plain_value) @font-lock-variable-name-face
+              (plain_value) @mhtml-ts-mode--colorize-css-value))
+           css--treesit-settings))
+  "Settings for `treesit-font-lock-settings'.")
+
+(defvar mhtml-ts-mode--treesit-thing-settings
+  ;; In addition to putting together the various definitions, we need to
+  ;; add 'defun' which is used to support `imenu' and 'which-function'.
+  (list
+   ;; HTML thing settings
+   (append
+    (car html-ts-mode--treesit-things-settings)
+    `((defun ,(regexp-opt (list html-ts-mode--treesit-defun-type-regexp)))=
))
+   ;; Javascript thing settings
+   (append
+    (car js--treesit-thing-settings)
+    `((defun ,js--treesit-defun-type-regexp)))
+   ;; CSS thing settings
+   `(css
+     (defun ,(regexp-opt (list css--treesit-defun-type-regexp)))))
+  "Settings for `treesit-thing-settings'.")
+
+(defvar mhtml-ts-mode--treesit-indent-rules
+  (treesit--indent-rules-optimize
+   (append html-ts-mode--indent-rules
+           ;; Extended rules for js and css, to
+           ;; indent appropriately when injected
+           ;; into html
+           (treesit-modify-indent-rules
+            'javascript
+            `((javascript ((parent-is "program")
+                           mhtml-ts-mode--js-css-tag-bol
+                           mhtml-ts-mode--js-css-indent-offset)))
+            js--treesit-indent-rules
+            :replace)
+           (treesit-modify-indent-rules
+            'css
+            `((css ((parent-is "stylesheet")
+                    mhtml-ts-mode--js-css-tag-bol
+                    mhtml-ts-mode--js-css-indent-offset)))
+            css--treesit-indent-rules 'prepend)
+           :replace))
+  "Settings for `treesit-simple-indent-rules'.")
+
+(defvar mhtml-ts-mode--treesit-aggregated-simple-imenu-settings
+  `((html ,@html-ts-mode--treesit-simple-imenu-settings)
+    (javascript ,@js--treesit-simple-imenu-settings)
+    (css ,@css--treesit-simple-imenu-settings))
+  "Settings for `treesit-simple-imenu'.")
+
+;; TODO: treesit-defun-type-regexp should have an aggregated version,
+;; like treesit-aggregated-simple-imenu-settings. Otherwise we can't
+;; reuse the regex defined in the major mode we use.
+(defvar mhtml-ts-mode--treesit-defun-type-regexp
+  (regexp-opt '("class_declaration"
+                "method_definition"
+                "function_declaration"
+                "lexical_declaration"
+                "element"
+                "rule_set"))
+  "Settings for `treesit-defun-type-regexp'.")
+
+;; In order to support `prettify-symbols-mode', just `append' the prettify
+;; alist of all the languages. In our case only javascript defined this al=
ist.
+(defvar mhtml-ts-mode--prettify-symbols-alist js--prettify-symbols-alist
+  "Alist of symbol prettifications for various supported languages.")
+
+(defun mhtml-ts-mode--html-defun-name (node)
+  "Return the defun name of NODE.
+Return nil if there is no name or if NODE is not a defun node."
+  (when (string-match-p "element" (treesit-node-type node))
+    (treesit-node-text
+     node
+     ;; (treesit-search-subtree node "\\`tag_name\\'" nil nil 2)
+     t)))
+
+;; In order to support `which-fuction-mode' we should define
+;; a function that return the defun name.
+;; In a multilingual treesit mode, this can be implemented simply by
+;; calling language-specific functions.
+(defun mhtml-ts-mode--defun-name (node)
+  "Return the defun name of NODE.
+Return nil if there is no name or if NODE is not a defun node."
+  (let ((html-name (html-ts-mode--defun-name node))
+        (js-name (js--treesit-defun-name node))
+        (css-name (css--treesit-defun-name node)))
+    (cond
+     (html-name html-name)
+     (js-name js-name)
+     (css-name css-name))))
+
+;;; Flymake integration
+
+(defvar-local mhtml-ts-mode--flymake-process nil
+  "Store the Flymake process.")
+
+(defun mhtml-ts-mode-flymake-mhtml (report-fn &rest _args)
+  "MHTML backend for Flymake.
+Calls REPORT-FN directly.  Requires tidy."
+  (when (process-live-p mhtml-ts-mode--flymake-process)
+    (kill-process mhtml-ts-mode--flymake-process))
+  (let ((tidy (executable-find "tidy"))
+        (source (current-buffer))
+        (diagnostics-pattern (eval-when-compile
+                               (rx bol
+                                   "line " (group (+ num))    ;; :1 line
+                                   " column " (group (+ num)) ;; :2 column
+                                   " - " (group (+? nonl))    ;; :3 type
+                                   ": " (group (+? nonl))     ;; :4 msg
+                                   eol))))
+    (if (not tidy)
+        (error "Unable to find tidy command")
+      (save-restriction
+        (widen)
+        (setq mhtml-ts-mode--flymake-process
+              (make-process
+               :name "mhtml-ts-mode-flymake"
+               :noquery t
+               :connection-type 'pipe
+               :buffer (generate-new-buffer "*mhtml-ts-mode-flymake*")
+               :command `(,tidy "--gnu-emacs" "yes" "-e" "-q")
+               :sentinel
+               (lambda (proc _event)
+                 (when (eq 'exit (process-status proc))
+                   (unwind-protect
+                       (if (with-current-buffer source
+                             (eq proc mhtml-ts-mode--flymake-process))
+                           (with-current-buffer (process-buffer proc)
+                             (goto-char (point-min))
+                             (let (diags)
+                               (while (search-forward-regexp diagnostics-p=
attern nil t)
+                                 (let* ((pos
+                                         (flymake-diag-region
+                                          source
+                                          (string-to-number (match-string =
1))
+                                          (string-to-number (match-string =
2)))) ;; line and column
+                                        (type (cond ((equal (match-string =
3) "Warning") :warning)
+                                                    ((equal (match-string =
3) "Error") :error))) ;; type of message
+                                        (msg (match-string 4))) ;; message
+                                   (push (flymake-make-diagnostic source (=
car pos) (cdr pos) type msg)
+                                         diags)))
+                               (funcall report-fn diags)))
+                         (flymake-log :warning "Canceling obsolete check %=
s" proc))
+                     (kill-buffer (process-buffer proc)))))))
+        (process-send-region mhtml-ts-mode--flymake-process (point-min) (p=
oint-max))
+        (process-send-eof mhtml-ts-mode--flymake-process)))))
+
+(define-derived-mode mhtml-ts-mode html-ts-mode
+  '("HTML+" (:eval (let ((lang (mhtml-ts-mode--language-at-point (point))))
+                     (cond ((eq lang 'html) "")
+                           ((eq lang 'javascript) "JS")
+                           ((eq lang 'css) "CSS")))))
+  "Major mode for editing HTML with embedded JavaScript and CSS.
+Powered by tree-sitter."
+  (if (not (and
+            (treesit-ready-p 'html)
+            (treesit-ready-p 'javascript)
+            (treesit-ready-p 'css)))
+      (error "Tree-sitter parsers for HTML isn't available.  You can
+    install the parsers with M-x `mhtml-ts-mode-install-parsers'")
+
+    ;; When an language is embedded, you should initialize some variable
+    ;; just like it's done in the original mode.
+
+    ;; Comment.
+    ;; indenting settings for js-ts-mode.
+    (c-ts-common-comment-setup)
+    (setq-local comment-multi-line t)
+
+    ;; Font-lock.
+
+    ;; There are two ways to handle embedded code:
+    ;; 1. Use a single parser for all the embedded code in the buffer. In
+    ;; this case, the embedded code blocks are concatenated together and a=
re
+    ;; seen as a single continuous document to the parser.
+    ;; 2. Each embedded code block gets its own parser. Each parser only s=
ees
+    ;; that particular code block.
+
+    ;; If you go with 2 for a language, the local parsers are created and
+    ;; destroyed automatically by Emacs. So don't create a global parser f=
or
+    ;; that embedded language here.
+
+    ;; Create the parsers, only the global ones.
+    ;; jsdoc is a local parser, don't create a parser for it.
+    (treesit-parser-create 'css)
+    (treesit-parser-create 'javascript)
+
+    ;; Multi-language modes must set the  primary parser.
+    (setq-local treesit-primary-parser (treesit-parser-create 'html))
+
+    (setq-local treesit-range-settings
+                (treesit-range-rules
+                 :embed 'javascript
+                 :host 'html
+                 '((script_element
+                    (start_tag (tag_name))
+                    (raw_text) @cap))
+
+                 ;; Another rule could be added that when it matches an
+                 ;; attribute_value that has as its parent an
+                 ;; attribute_name "style" it captures it and then
+                 ;; passes it to the css parser.
+                 :embed 'css
+                 :host 'html
+                 '((style_element
+                    (start_tag (tag_name))
+                    (raw_text) @cap))))
+
+    ;; jsdoc is not mandatory for js-ts-mode, so we respect this by
+    ;; adding jsdoc range rules only when jsdoc is available.
+    (when (treesit-ready-p 'jsdoc t)
+      (setq-local treesit-range-settings
+                  (append treesit-range-settings
+                          (treesit-range-rules
+                           :embed 'jsdoc
+                           :host 'javascript
+                           :local t
+                           `(((comment) @cap
+                              (:match ,js--treesit-jsdoc-beginning-regexp =
@cap))))))
+      (setq-local c-ts-common--comment-regexp
+                  js--treesit-jsdoc-comment-regexp))
+
+
+    ;; Many treesit fuctions need to know the language at-point.
+    ;; So you should define such a function.
+    (setq-local treesit-language-at-point-function #'mhtml-ts-mode--langua=
ge-at-point)
+    (setq-local prettify-symbols-alist mhtml-ts-mode--prettify-symbols-ali=
st)
+
+    ;; Indent.
+
+    ;; Since `mhtml-ts-mode' inherits indentation rules from `html-ts-mode=
', `js-ts-mode'
+    ;; and `css-ts-mode', if you want to change the offset you have to act=
 on the
+    ;; *-offset variables defined for those languages.
+
+    ;; JavaScript and CSS must be indented relative to their code block.
+    ;; This is done by inserting a special rule before the normal
+    ;; indentation rules of these languages.
+    ;; The value of `mhtml-ts-mode--js-css-indent-offset' changes based on
+    ;; `mhtml-ts-mode-tag-relative-indent' and can be used to indent
+    ;; JavaScript and CSS code relative to the HTML that contains them,
+    ;; just like in mhtml-mode.
+    (setq-local treesit-simple-indent-rules mhtml-ts-mode--treesit-indent-=
rules)
+
+    ;; Navigation.
+
+    ;; This is for which-function-mode.
+    ;; Since mhtml-ts-mode is derived from html-ts-mode, which sets
+    ;; the value of `treesit-defun-type-regexp', you have to reset it to n=
il
+    ;; otherwise `imenu' and `which-function-mode' will not work.
+    (setq-local treesit-defun-type-regexp nil)
+
+    ;; This is for finding defun name, it's used by IMenu as default
+    ;; function no specific functions are defined.
+    (setq-local treesit-defun-name-function #'mhtml-ts-mode--defun-name)
+
+    ;; Define what are 'thing' for treesit.
+    ;; 'Thing' is a symbol representing the thing, like `defun', `sexp', or
+    ;; `sentence'.
+    ;; As an alternative, if you want just defun, you can define a `treesi=
t-defun-type-regexp'.
+    (setq-local treesit-thing-settings mhtml-ts-mode--treesit-thing-settin=
gs)
+
+    ;; Font-lock.
+
+    ;; In a multi-language scenario, font lock settings are usually a
+    ;; concatenation of language rules. As you can see, it is possible
+    ;; to extend/modify the default rule or use a different set of
+    ;; rules. See `php-ts-mode--custom-html-font-lock-settings' for more
+    ;; advanced usage.
+    (setq-local treesit-font-lock-settings mhtml-ts-mode--treesit-font-loc=
k-settings)
+
+    ;; Tells treesit the list of features to fontify.
+    (setq-local treesit-font-lock-feature-list mhtml-ts-mode--treesit-font=
=2Dlock-feature-list)
+
+    ;; Imenu
+
+    ;; Setup Imenu: if no function is specified, try to find an object
+    ;; using `treesit-defun-name-function'.
+    (setq-local treesit-aggregated-simple-imenu-settings
+                mhtml-ts-mode--treesit-aggregated-simple-imenu-settings)
+
+    ;; (setq-local treesit-outline-predicate nil)
+
+    (treesit-major-mode-setup)
+
+    ;; This is sort of a prog-mode as well as a text mode.
+    (run-mode-hooks 'prog-mode-hook)
+
+    ;; Flymake
+    (add-hook 'flymake-diagnostic-functions #'mhtml-ts-mode-flymake-mhtml =
nil 'local)))
+
+;; Add nome extra parents.
+(derived-mode-add-parents 'mhtml-ts-mode '(css-mode js-mode))
+
+(when (and (treesit-ready-p 'html) (treesit-ready-p 'javascript) (treesit-=
ready-p 'css))
+  (add-to-list
+   'auto-mode-alist '("\\.[sx]?html?\\(\\.[a-zA-Z_]+\\)?\\'" . mhtml-ts-mo=
de)))
+
+(provide 'mhtml-ts-mode)
+;;; mhtml-ts-mode.el ends here
diff --git a/lisp/treesit.el b/lisp/treesit.el
index b923545d50c..30fe3798bd9 100644
=2D-- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -1317,6 +1317,40 @@ treesit-font-lock-recompute-features
                        ((memq feature remove-list) nil)
                        (t current-value))))))
=20
+(defun treesit-merge-font-lock-feature-list (features-list-1 features-list=
=2D2)
+  "Merge two tree-sitter font lock feature lists.
+Returns a new font lock feature list with no duplicates in the same level.
+It can be used to merge font lock feature lists in a multi-language major =
mode.
+FEATURES-LIST-1 and FEATURES-LIST-2 are list of lists of feature symbols."
+    (let ((result nil)
+	(features-1 (car features-list-1))
+	(features-2 (car features-list-2)))
+    (while (or features-1 features-2)
+      (cond
+       ((and features-1 (not features-2)) (push features-1 result))
+       ((and (not features-1) features-2) (push features-2 result))
+       ((and features-1 features-2) (push (cl-union features-1 features-2)=
 result)))
+      (setq features-list-1 (cdr features-list-1)
+	    features-list-2 (cdr features-list-2)
+	    features-1 (car features-list-1)
+            features-2 (car features-list-2)))
+    (nreverse result)))
+
+(defun treesit-replace-font-lock-feature-settings (new-settings settings)
+  "Replaces :feature in SETTINGS with :feature from NEW-SETTINGS.
+Both SETTINGS and NEW-SETTINGS must be a value suitable for
+`treesit-font-lock-settings'.
+Return a value suitable for `treesit-font-lock-settings'"
+  (let ((result nil))
+    (dolist (new-setting new-settings)
+      (let ((new-feature (treesit-font-lock-setting-feature new-setting)))
+	(dolist (setting settings)
+	  (let ((feature (treesit-font-lock-setting-feature setting)))
+	    (if (eq new-feature feature)
+		(push new-setting result)
+	      (push setting result))))))
+    (nreverse result)))
+
 (defun treesit-add-font-lock-rules (rules &optional how feature)
   "Add font-lock RULES to the current buffer.
=20
@@ -2506,6 +2540,40 @@ treesit-add-simple-indent-rules
               (append rules existing-rules)))))
     (setf (alist-get language treesit-simple-indent-rules) new-rules)))
=20
+(defun treesit-modify-indent-rules (lang new-rules rules &optional how)
+  "Modify a copy of RULES using NEW-RULES.
+As default replace rules with the same anchor.
+When HOW is :prepend NEW-RULES are prepend to RULES, when
+:append NEW-RULES are appended to RULES, when :replace (the default)
+NEW-RULES replace rule in RULES which the same anchor."
+  (cond
+   ((not (alist-get lang rules))
+    (error "No rules for language %s in RULES" lang))
+   ((not (alist-get lang new-rules))
+    (error "No rules for language %s in NEW-RULES" lang))
+   (t (let* ((copy-of-rules (copy-tree js--treesit-indent-rules))
+	     (lang-rules (alist-get lang copy-of-rules))
+	     (lang-new-rules (alist-get lang new-rules)))
+	(cond
+	 ((eq how :prepend)
+	  (setf (alist-get lang copy-of-rules)
+		(append lang-new-rules lang-rules)))
+	 ((eq how :append)
+	  (setf (alist-get lang copy-of-rules)
+		(append lang-rules lang-new-rules)))
+	 ((or (eq how :replace) t)
+	  (let ((tail-new-rules lang-new-rules)
+		(tail-rules lang-rules)
+		(new-rule nil)
+		(rule nil))
+	    (while (setq new-rule (car tail-new-rules))
+	      (while (setq rule (car tail-rules))
+		(when (equal (nth 0 new-rule) (nth 0 rule))
+		  (setf (car tail-rules) new-rule))
+		(setq tail-rules (cdr tail-rules)))
+	      (setq tail-new-rules (cdr tail-new-rules))))))
+	copy-of-rules))))
+
 ;;; Search
=20
 (defun treesit-search-forward-goto
=2D-=20
2.48.1


--nextPart3611817.dWV9SEqChM--







Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 13 Feb 2025 18:03:04 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu Feb 13 13:03:04 2025
Received: from localhost ([127.0.0.1]:45391 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tidYG-00025j-4n
	for submit <at> debbugs.gnu.org; Thu, 13 Feb 2025 13:03:04 -0500
Received: from relay7-d.mail.gandi.net ([217.70.183.200]:52383)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <juri@HIDDEN>) id 1tidYC-00024s-0O
 for 74610 <at> debbugs.gnu.org; Thu, 13 Feb 2025 13:03:00 -0500
Received: by mail.gandi.net (Postfix) with ESMTPSA id 968DF441E6;
 Thu, 13 Feb 2025 18:02:51 +0000 (UTC)
From: Juri Linkov <juri@HIDDEN>
To: Vincenzo Pupillo <v.pupillo@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
In-Reply-To: <5707421.ZASKD2KPVS@fedora>
Organization: LINKOV.NET
References: <3532547.LZWGnKmheA@fedora> <4931998.OV4Wx5bFTl@fedora>
 <87frkn57ho.fsf@HIDDEN> <5707421.ZASKD2KPVS@fedora>
Date: Thu, 13 Feb 2025 19:55:59 +0200
Message-ID: <87zfip685s.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/31.0.50 (x86_64-pc-linux-gnu)
MIME-Version: 1.0
Content-Type: text/plain
X-GND-State: clean
X-GND-Score: -100
X-GND-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgdegjeeggecutefuodetggdotefrodftvfcurfhrohhfihhlvgemucfitefpfffkpdcuggftfghnshhusghstghrihgsvgenuceurghilhhouhhtmecufedtudenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujfgurhephffvvefujghofhffkfgfgggtsehttdertddtredtnecuhfhrohhmpefluhhrihcunfhinhhkohhvuceojhhurhhisehlihhnkhhovhdrnhgvtheqnecuggftrfgrthhtvghrnhepffegteefveelhfeljeefueehieduiedtfffhuddtkeeffffghfevheetgeeukeehnecukfhppeeluddruddvledrleekrdehnecuvehluhhsthgvrhfuihiivgepudenucfrrghrrghmpehinhgvthepledurdduvdelrdelkedrhedphhgvlhhopehmrghilhdrghgrnhguihdrnhgvthdpmhgrihhlfhhrohhmpehjuhhriheslhhinhhkohhvrdhnvghtpdhnsggprhgtphhtthhopeegpdhrtghpthhtohepjeegiedutdesuggvsggsuhhgshdrghhnuhdrohhrghdprhgtphhtthhopegvlhhiiiesghhnuhdrohhrghdprhgtphhtthhopegtrghsohhurhhisehgmhgrihhlrdgtohhmpdhrtghpthhtohepvhdrphhuphhilhhlohesghhmrghilhdrtghomh
X-GND-Sasl: juri@HIDDEN
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 74610
Cc: casouri@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

>> I think we need now clarify the relation
>> between mhtml-ts-mode and html-ts-mode.
>> 
>> For example, currently I added a new function
>> 'html-ts-mode--outline-predicate' to html-ts-mode.el.
>> Should it be used in mhtml-ts-mode.el as well?
> I have to try but I think so, mhtml-ts-mode is declared as derived from html-
> ts-mode (although it is not very clear to me how derived modes work). The 
> treesit-outline-predicate variable is set by html-ts-mode and mhtml-ts-mode 
> inherits the same value. 

It seems inheritance doesn't work for embedded ts-modes
since even the primary ts-mode should be configured
by aggregation like with treesit-aggregated-simple-imenu-settings,
not by inheritance.

I see that you added TODO for an aggregated version of
treesit-defun-type-regexp.  Then treesit-outline-predicate
also needs an aggregated version, and maybe other settings too.
Ok, I could look how to do this with treesit-outline-predicate.

>> What about other settings?  Should html-ts-mode and mhtml-ts-mode
>> always be kept in sync?  Or html-ts-mode should be obsoleted
>> when it will be superseded by mhtml-ts-mode?
>> 
>> Maybe html-ts-mode would be still needed as a separate mode
>> to be used as embedded submode in such files as e.g.
>> Vue single-file component with *.vue files that contain parts
>> from js-ts-mode, css-ts-mode, and html-ts-mode for templates.
>
> I don't know. In general, I think having simple major modes makes it easier to 
> build more complex aggregations, rather than having to disable some features 
> inherited from complex major modes, as in html-ts-mode: "
>  ;; `html-ts-mode' inherits from `html-mode' that sets
>   ;; regexp-based outline variables.  So need to restore
>   ;; the default values of outline variables to be able
>   ;; to use `treesit-outline-predicate' above.

I checked that neither aggregation nor inheritance from html-ts-mode
can be used for vue-ts-mode since tree-sitter-vue duplicates
HTML grammar in its own grammar.  Even tree-sitter-astro
incorporates a simplified HTML grammar into its own grammar.
This is similar to HTML-like jsx elements in tsx grammar.




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 9 Feb 2025 21:23:14 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 16:23:14 2025
Received: from localhost ([127.0.0.1]:46686 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1thElm-0000bQ-8b
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 16:23:14 -0500
Received: from mail-ej1-x629.google.com ([2a00:1450:4864:20::629]:48424)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <v.pupillo@HIDDEN>)
 id 1thElj-0000b4-Tp
 for 74610 <at> debbugs.gnu.org; Sun, 09 Feb 2025 16:23:12 -0500
Received: by mail-ej1-x629.google.com with SMTP id
 a640c23a62f3a-aaf3c3c104fso659659266b.1
 for <74610 <at> debbugs.gnu.org>; Sun, 09 Feb 2025 13:23:11 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1739136186; x=1739740986; darn=debbugs.gnu.org;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:from:to:cc:subject:date
 :message-id:reply-to;
 bh=S/HibITPVzroRP/zCJSLfi/PRD4tZaiAmW7DavKk+ow=;
 b=bDb8dDoPWh0XVBDeCAZPLNVaWUtFsKPpqb8OZx1k5BzRlozIl4mhk0muIF8hHsHRd+
 sZ2EzsI8/F7UnpKFEQr6piJtNGJimgrFFcPLLBU+MV7RPxXKi6Ff9gfONlIYnNTScmHf
 tghQen6E/xFKBG751YRxPFy2/aPra3GjRutl5Fp4Tj5dnD4Q2gds9TM1veMauGIGBHlb
 BfUdiSKpcdN+LRb3HZyffZuKRuRLIYn81rbRBrs7D7X8+5zpAwt1z36dODF5envayz7W
 4S3tMmfNXFeiR7gbJ8dOA4CCZwxrGn2KbpuyKnFmFH5LkL+BizIa91oOFbXn7DX7XgEo
 0dNA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1739136186; x=1739740986;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=S/HibITPVzroRP/zCJSLfi/PRD4tZaiAmW7DavKk+ow=;
 b=ehIYOZx1yxVyRldka2fOw4ZH31v51APDtUgIBNbeK2dm6/goGvUWYI7LY52sQm5lXx
 QdViFR7Jz/L5qrT57cKD4aG4ChjR7GL9oqxOF7aXhXrxrpSpv57rtcsuV8itMfz9RjtT
 7C1aZwqun+EAZQ6ZoNSpDnlCi/tYyDOmzlui5otc6jiqAvQyjpIcYsmyLBpEdbMovfrS
 wxiFjGHDv1C8ZKhwR3PYmr+B/WpmGR+kgj1PpYWVpORpP2kt3kIKVnFcUdWXStiZQotf
 na5rLMPjWYGUtLAI4XHLslUlq2LBrmcj8KAJ0275utOHZASH18ca73ZkJoyc3xApTfQh
 /DsA==
X-Forwarded-Encrypted: i=1;
 AJvYcCUhg1JUUiwhbcI8boZkgPDUzoxmtAKgXrxwu8a/KZCA+Xwj5JqIj4XaqjPBs77Ir5jwQ5/btQ==@debbugs.gnu.org
X-Gm-Message-State: AOJu0YzwiMYvREaubrsgPLMjDiwj8TpZJUMQDNxqy0dONMaVl52/qYCT
 GIdgTmKzb/cal9ro/R/N/XiGBBdm4tYKhy8M+TCKbueLkt3Z2Eic
X-Gm-Gg: ASbGncsxPO2lHiJmtz2+SBzzrateeNX1l/r4exhQ/ncXkgo6bBhH8FzN7F+nxOKIp0J
 TKXEaVgQk06bkZQ7V/pNZRIT6vPmwmf6p2buj17lc+7MI4Rjo30eyIQRHLbOyNEwBpw4/N/By7S
 IyjDPzGsPFVnPKXh0xap70Gt2oyiow+1RIFfBEWufhxmbDGI8uQ2TbzaM233DUvzLYQGxIL0BvI
 MMrDptss8m2V9QDAuf7IsO43v4fCDVY2WvpSETSIqjCPhcF2U/ZV2/Oyhs2WtvZ1Rr15keAA/Zj
 OAyM9NAtqz0eYoIH2j51bfQUUbhRmJbM2K9av4j8ZQg2SLCZuSbDHPo=
X-Google-Smtp-Source: AGHT+IFV+OOTqOe4vdSfDoy+toyA6LX/2h8oA1O1Tw7QG6gGxvTbmsQcqftNNOPErdFByPgMtjxCnA==
X-Received: by 2002:a17:907:1c22:b0:ab7:647:d52d with SMTP id
 a640c23a62f3a-ab789c6e8c9mr1168812966b.51.1739136185298; 
 Sun, 09 Feb 2025 13:23:05 -0800 (PST)
Received: from fedora.localnet (2-230-139-124.ip202.fastwebnet.it.
 [2.230.139.124]) by smtp.gmail.com with ESMTPSA id
 a640c23a62f3a-ab772f843b4sm729070566b.42.2025.02.09.13.23.04
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Sun, 09 Feb 2025 13:23:04 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: Juri Linkov <juri@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Sun, 09 Feb 2025 22:23:03 +0100
Message-ID: <5707421.ZASKD2KPVS@fedora>
In-Reply-To: <87frkn57ho.fsf@HIDDEN>
References: <3532547.LZWGnKmheA@fedora> <4931998.OV4Wx5bFTl@fedora>
 <87frkn57ho.fsf@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: casouri@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Ciao Juri,

In data domenica 9 febbraio 2025 18:54:27 Ora standard dell=E2=80=99Europa =
centrale,=20
Juri Linkov ha scritto:
> > Ciao Eli,
> > This attached patch is the third version. Adapted to support the latest
> > changes introduced in treesit.el and js.el (as described in bug#75456).
> > @Jury, @Yuan what do you think about this version?
>=20
> Thanks for the patch.
>=20
> I think we need now clarify the relation
> between mhtml-ts-mode and html-ts-mode.
>=20
> For example, currently I added a new function
> 'html-ts-mode--outline-predicate' to html-ts-mode.el.
> Should it be used in mhtml-ts-mode.el as well?
I have to try but I think so, mhtml-ts-mode is declared as derived from htm=
l-
ts-mode (although it is not very clear to me how derived modes work). The=20
treesit-outline-predicate variable is set by html-ts-mode and mhtml-ts-mode=
=20
inherits the same value.=20

>=20
> What about other settings?  Should html-ts-mode and mhtml-ts-mode
> always be kept in sync?  Or html-ts-mode should be obsoleted
> when it will be superseded by mhtml-ts-mode?
>=20
> Maybe html-ts-mode would be still needed as a separate mode
> to be used as embedded submode in such files as e.g.
> Vue single-file component with *.vue files that contain parts
> from js-ts-mode, css-ts-mode, and html-ts-mode for templates.

I don't know. In general, I think having simple major modes makes it easier=
 to=20
build more complex aggregations, rather than having to disable some feature=
s=20
inherited from complex major modes, as in html-ts-mode: "
 ;; `html-ts-mode' inherits from `html-mode' that sets
  ;; regexp-based outline variables.  So need to restore
  ;; the default values of outline variables to be able
  ;; to use `treesit-outline-predicate' above.
"

Vincenzo









Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 9 Feb 2025 17:56:45 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 12:56:45 2025
Received: from localhost ([127.0.0.1]:46166 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1thBXx-0007W2-Av
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 12:56:45 -0500
Received: from relay3-d.mail.gandi.net ([217.70.183.195]:35975)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <juri@HIDDEN>) id 1thBXv-0007Vj-4r
 for 74610 <at> debbugs.gnu.org; Sun, 09 Feb 2025 12:56:43 -0500
Received: by mail.gandi.net (Postfix) with ESMTPSA id AF3152047A;
 Sun,  9 Feb 2025 17:56:34 +0000 (UTC)
From: Juri Linkov <juri@HIDDEN>
To: Vincenzo Pupillo <v.pupillo@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
In-Reply-To: <4931998.OV4Wx5bFTl@fedora>
Organization: LINKOV.NET
References: <3532547.LZWGnKmheA@fedora> <26756353.1r3eYUQgxm@fedora>
 <864j144xoj.fsf@HIDDEN> <4931998.OV4Wx5bFTl@fedora>
Date: Sun, 09 Feb 2025 19:54:27 +0200
Message-ID: <87frkn57ho.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/31.0.50 (x86_64-pc-linux-gnu)
MIME-Version: 1.0
Content-Type: text/plain
X-GND-State: clean
X-GND-Score: -100
X-GND-Cause: gggruggvucftvghtrhhoucdtuddrgeefvddrtddtgdefheekudcutefuodetggdotefrodftvfcurfhrohhfihhlvgemucfitefpfffkpdcuggftfghnshhusghstghrihgsvgenuceurghilhhouhhtmecufedtudenucesvcftvggtihhpihgvnhhtshculddquddttddmnecujfgurhephffvvefujghofhffkfgfgggtsehttdertddtredtnecuhfhrohhmpefluhhrihcunfhinhhkohhvuceojhhurhhisehlihhnkhhovhdrnhgvtheqnecuggftrfgrthhtvghrnhepffegteefveelhfeljeefueehieduiedtfffhuddtkeeffffghfevheetgeeukeehnecukfhppeeluddruddvledrleeirdeltdenucevlhhushhtvghrufhiiigvpedtnecurfgrrhgrmhepihhnvghtpeeluddruddvledrleeirdeltddphhgvlhhopehmrghilhdrghgrnhguihdrnhgvthdpmhgrihhlfhhrohhmpehjuhhriheslhhinhhkohhvrdhnvghtpdhnsggprhgtphhtthhopeegpdhrtghpthhtohepjeegiedutdesuggvsggsuhhgshdrghhnuhdrohhrghdprhgtphhtthhopegvlhhiiiesghhnuhdrohhrghdprhgtphhtthhopegtrghsohhurhhisehgmhgrihhlrdgtohhmpdhrtghpthhtohepvhdrphhuphhilhhlohesghhmrghilhdrtghomh
X-GND-Sasl: juri@HIDDEN
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 74610
Cc: casouri@HIDDEN, Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

> Ciao Eli,
> This attached patch is the third version. Adapted to support the latest 
> changes introduced in treesit.el and js.el (as described in bug#75456).
> @Jury, @Yuan what do you think about this version?

Thanks for the patch.  

I think we need now clarify the relation
between mhtml-ts-mode and html-ts-mode.

For example, currently I added a new function
'html-ts-mode--outline-predicate' to html-ts-mode.el.
Should it be used in mhtml-ts-mode.el as well?

What about other settings?  Should html-ts-mode and mhtml-ts-mode
always be kept in sync?  Or html-ts-mode should be obsoleted
when it will be superseded by mhtml-ts-mode?

Maybe html-ts-mode would be still needed as a separate mode
to be used as embedded submode in such files as e.g.
Vue single-file component with *.vue files that contain parts
from js-ts-mode, css-ts-mode, and html-ts-mode for templates.




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 9 Feb 2025 14:36:12 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Feb 09 09:36:12 2025
Received: from localhost ([127.0.0.1]:43679 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1th8Po-0008La-KL
	for submit <at> debbugs.gnu.org; Sun, 09 Feb 2025 09:36:12 -0500
Received: from mail-ed1-x535.google.com ([2a00:1450:4864:20::535]:49351)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <v.pupillo@HIDDEN>)
 id 1th8PZ-0008Ky-Ro
 for 74610 <at> debbugs.gnu.org; Sun, 09 Feb 2025 09:36:04 -0500
Received: by mail-ed1-x535.google.com with SMTP id
 4fb4d7f45d1cf-5dcef33eeceso6669763a12.2
 for <74610 <at> debbugs.gnu.org>; Sun, 09 Feb 2025 06:35:53 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1739111747; x=1739716547; darn=debbugs.gnu.org;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:from:to:cc:subject:date
 :message-id:reply-to;
 bh=JbazYI6aZoDMgtRRmk+qy1uKtlJacS0/JlZcHJ5k3/w=;
 b=IljGUuKcKckcSlhYsjJx/WB9OOGIKkV3o7gJvwYB6QM2qw6+SqKW8ig5wbKSbKplnW
 49JAIAn/pvsU1MdIfnh5XCKmzxU72L8CV+wBWOJHMdE/12MvUIAqI8aNPBKfwmscOsCx
 RxbJNX3OL5AAdo2NJ9otMAE58nze7nnOqqSsth0YFTz/qWq4wU9AiCEcobEm0hrJapI3
 3XB/0qN6TxqtePN1DrtcUynMPDNB9m/wZvMx0TlzeXQXj4BuLyH6tq7J9wPPOsbJCEz7
 OyivuuBU+VDzs7HZuWQcCGMDpjvTKrwIWFLhE03LS5vzKIzWx4LVZGfKCJPEFumsrSw3
 hxvA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1739111747; x=1739716547;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=JbazYI6aZoDMgtRRmk+qy1uKtlJacS0/JlZcHJ5k3/w=;
 b=XWHIFQWkPxwxxYgy1VFoiASufvQsijhA7fX2nn5RtCCHXBKHfpMqbWajQ6Mu9SDEQo
 6ynI7E8t6TU5va4e/v/SIDOxN6veiV8QtZXWc/ZAwE1+2CvHjc6KyUdDRR/TniB9+0nQ
 ohNotFdJDXWtjogStU7swjcTzG7zICA5o9dWAaWu9awot7lQd05M9b8AkTkoM0CB/yBM
 Y65n0aw6Dt2j5bAmrOShghfNsjMHMe67CnGwN/T4fo7rvoldFXyGM9EIwGEyNjkYxvUE
 tZKkKlkrfrQdXHHOlu2aV/7YU/aeQl1+3p0AxdwVEd7WbKCsqA5dY+2jr/7Tqf85zLdo
 g9IA==
X-Gm-Message-State: AOJu0Yw3wacHzY9Z3QzSL+EqcvB9JIXBUEu8l+mWyJmQOQMo1dFODHzd
 NzjbgI7AxT6ZI+rte+7Nhq/LA72MviCQI6+/7PCWBvt5jCA79B3J
X-Gm-Gg: ASbGnctpde3OEnxSn7z8/hSH52uJwjkrAZbrxrNFxKhIiVV8KKN1faYEg4NPGALqhAA
 KUYJb1qpxoyLVSgdM2MU9id/oVvICcz86jUppypTlZX++8GTQXiUUKl9SCHUMow4ICieYQiVv6K
 2zl1n+aVwaKoeLSH786xABeCDFrI9841EAK/1s/IJqJsi/E9OgcQKhGP78yv5GqQm/zZBXMGnz+
 CZOdxBp/KRBcBqiFTdmzm1raUO9MY38G333wPGG4joZ702nAdcigKhrv6SXAmU7tdUiTdao4bm/
 7ryTcXczwYHZMNSubZvDZ4N21SsbSxxVS9vpFqCgH9GaHnbyb8Qj7iY=
X-Google-Smtp-Source: AGHT+IHwCwVTkuT15QrNcDMCsMFh8Y9Mm2+DY2r/WtEfeLdD+wbYb0krkqrq3KJv3lEm86yhtot96Q==
X-Received: by 2002:a05:6402:321d:b0:5dc:cf9b:b048 with SMTP id
 4fb4d7f45d1cf-5de45040066mr11515507a12.1.1739111746447; 
 Sun, 09 Feb 2025 06:35:46 -0800 (PST)
Received: from fedora.localnet (2-230-139-124.ip202.fastwebnet.it.
 [2.230.139.124]) by smtp.gmail.com with ESMTPSA id
 4fb4d7f45d1cf-5dcf9f6c1d6sm6110909a12.65.2025.02.09.06.35.44
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Sun, 09 Feb 2025 06:35:45 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: casouri@HIDDEN, juri@HIDDEN, Eli Zaretskii <eliz@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Sun, 09 Feb 2025 15:35:44 +0100
Message-ID: <4931998.OV4Wx5bFTl@fedora>
In-Reply-To: <864j144xoj.fsf@HIDDEN>
References: <3532547.LZWGnKmheA@fedora> <26756353.1r3eYUQgxm@fedora>
 <864j144xoj.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="nextPart26760901.1r3eYUQgxm"
Content-Transfer-Encoding: 7Bit
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This is a multi-part message in MIME format.

--nextPart26760901.1r3eYUQgxm
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"

Ciao Eli,
This attached patch is the third version. Adapted to support the latest=20
changes introduced in treesit.el and js.el (as described in bug#75456).
@Jury, @Yuan what do you think about this version?

Thanks.

Vincenzo

In data sabato 8 febbraio 2025 10:01:48 Ora standard dell=E2=80=99Europa ce=
ntrale, Eli=20
Zaretskii ha scritto:
> Ping! Can we please make progress with this issue?  is the patch ready
> to be installed?
>=20
> > From: Vincenzo Pupillo <v.pupillo@HIDDEN>
> > Cc: Juri Linkov <juri@HIDDEN>, 74610 <at> debbugs.gnu.org,
> >=20
> >  Eli Zaretskii <eliz@HIDDEN>
> >=20
> > Date: Sun, 19 Jan 2025 18:09:31 +0100
> >=20
> > Ciao Yuan,
> > In data sabato 18 gennaio 2025 02:37:53 Ora standard dell=E2=80=99Europ=
a centrale,
> >=20
> > Yuan Fu ha scritto:
> > > > On Jan 14, 2025, at 1:41=E2=80=AFPM, Vincenzo Pupillo <v.pupillo@gm=
ail.com>
> > > > wrote:
> > > >=20
> > > > Ciao Yuan and Juri,
> > > > this is an updated version of mhtml-ts-mode.
> > > > I have tried to reduce as much as possible copies of parts of the
> > > > major
> > > > modes from which it is derived.
> > > > To do this, I had to move some values that were assigned directly to
> > > > treesit's own variables  (in ccs-mode.el, in js.el, and in
> > > > html-ts-mode.el) into new variables.
> > > > I also added three new functions to treesit.el to make it easier to
> > > > combine
> > > > parts derived from the other major modes. So now any changes to the=
se
> > > > new
> > > > variables are directly reflected in the behavior of mhtml-ts-mode.
> > > > There are a few things I would like to highlight:
> > > > 1. treesit-font-lock-feature-lists are not defined per parser, so
> > > > simply
> > > > merging the different lists will cause display differences from the
> > > > original major-modes; for example =E2=80=9Cfunction=E2=80=9D is def=
ined at level 3 in
> > > > css-ts-mode but at level 4 in js-ts-mode.
> > >=20
> > > Yeah, that=E2=80=99s not pretty, I don=E2=80=99t have a good solution=
 for now.
> > > Technically a major mode can use treesit-font-lock-recompute-features
> > > after
> > > treesit-major-mode-setup to do fine adjustments, but it=E2=80=99s ugl=
y.
> > >=20
> > > > 2. treesit-defun-type-regexp has the same problem as
> > > > treesit-font-lock-
> > > > feature-list, so I had to define it myself.
> > >=20
> > > Maybe you can use treesit-thing-settings instead?
> >=20
> > Ok, done.
> >=20
> > > > But other than that, it works pretty well.
> > > >=20
> > > > IMHO, a global "list" where you can define "font-lock", "indent-lis=
t",
> > > > "font- lock-feature", etc. by language (perhaps with getter and set=
ter
> > > > methods) might make it easier to define new multilingual major-mode=
s.
> > > > It
> > > > could improve the decoupling between multilingual major-modes and t=
he
> > > > major-modes they are derived from. It could also better decouple the
> > > > internal implementation of treesit.el from the treesitter-based
> > > > major-modes.
> > > >=20
> > > > Let me know what you think.
> > >=20
> > > That=E2=80=99s what I had in mind. That=E2=80=99s also why I added a =
new variable
> > > treesit-aggregated-simple-imenu-settings instead of piggybacking on
> > > treesit-simple-imenu-settings. A separate variable is simpler to borr=
ow
> > > settings from.
> >=20
> > Yes, in this new patch I used treesit-aggregated-simple-imenu-settings.
> >=20
> > > But I didn=E2=80=99t have time to think it though yet. (By language, =
or by mode
> > > and
> > > language? Should packages set it on load, or define a function that
> > > loads
> > > these settings? Etc.)
> >=20
> > I would say by mode and language, both mhtml-ts mode and html-ts mode h=
ave
> > the same primary parser. For the second question, I don't know. I don't
> > have enough experience with it to know what the pros and cons are.
> >=20
> > > Yuan
> >=20
> > Vincenzo
> >=20
> > From 199af27f1d761d100f6a8a6163c27e35e6ab2ea0 Mon Sep 17 00:00:00 2001
> > From: Vincenzo Pupillo <v.pupillo@HIDDEN>
> > Date: Sun, 19 Jan 2025 17:49:30 +0100
> > Subject: [PATCH] Add mhtml-ts-mode.
> >=20
> > New major-mode alternative to mhtml-mode, based on treesitter, for
> > editing files containing html, javascript and css.
> >=20
> > * etc/NEWS: Mention the new mode and new functions.
> > * lisp/textmodes/mhtml-ts-mode.el: New file.
> > * lisp/progmodes/js.el (js--treesit-thing-settings): New variable.
> > (js--treesit-font-lock-feature-list); New variable.
> > (js--treesit-simple-imenu-settings): New variable.
> > (js--treesit-defun-type-regexp): New variable.
> > (js--treesit-jsdoc-comment-regexp): New variable.
> > (js-ts-mode): Use of new variables instead of direct assignment of
> > values.
> > * lisp/textmodes/css-mode.el (css-mode--menu): New variable.
> > (css-mode-map): Use new variable.
> > (css--treesit-font-lock-feature-list): New variable.
> > (css--treesit-simple-imenu-settings): New variable.
> > (css--treesit-defun-type-regexp): New variable.
> > (cs-ts-mode): Use of new variables instead of direct assignment of
> > values.
> > * lisp/textmodes/html-ts-mode.el
> > (html-ts-mode--treesit-things-settings): New variable.
> > (html-ts-mode--treesit-font-lock-feature-list): New variable.
> > (html-ts-mode--treesit-simple-imenu-settings): New variable.
> > (html-ts-mode--treesit-defun-type-regexp): New variable.
> > (html-ts-mode): Use of new variables instead of direct assignment of
> > values.
> > * lisp/treesit.el
> > (treesit-merge-font-lock-feature-list): New fuction.
> > (treesit-replace-font-lock-feature-settings): New fuction.
> > (treesit-modify-indent-rules): New function.
> > ---
> >=20
> >  etc/NEWS                        |  28 ++
> >  lisp/progmodes/js.el            |  72 ++--
> >  lisp/textmodes/css-mode.el      |  47 ++-
> >  lisp/textmodes/html-ts-mode.el  |  45 ++-
> >  lisp/textmodes/mhtml-ts-mode.el | 600 ++++++++++++++++++++++++++++++++
> >  lisp/treesit.el                 |  65 ++++
> >  6 files changed, 798 insertions(+), 59 deletions(-)
> >  create mode 100644 lisp/textmodes/mhtml-ts-mode.el
> >=20
> > diff --git a/etc/NEWS b/etc/NEWS
> > index 0b849dec450..6360a082e9f 100644
> > --- a/etc/NEWS
> > +++ b/etc/NEWS
> >=20
> > @@ -965,6 +965,12 @@ destination window is chosen using 'display-buffer-
alist'.  Example:
> >  =0C
> >  * New Modes and Packages in Emacs 31.1
> >=20
> > +** New major modes based on the tree-sitter library
> > +
> > +*** New major mode 'mhtml-ts-mode'.
> > +An optional major mode based on the tree-sitter library for editing ht=
ml
> > +files. This mode handles indentation, fontification, and commenting for
> > +embedded JavaScript and CSS.
> >=20
> >  =0C
> >  * Incompatible Lisp Changes in Emacs 31.1
> >=20
> > @@ -1098,6 +1104,28 @@ language symbol.  For example, 'cpp' is translat=
ed
> > to "C++".  A new>=20
> >  variable 'treesit-language-display-name-alist' holds the translations =
of
> >  language symbols where that translation is not trivial.
> >=20
> > ++++
> > +*** New function 'treesit-merge-font-lock-feature-list'.
> > +This function the merge two tree-sitter font lock feature lists.
> > +Returns a new font lock feature list with no duplicates in the same
> > level.
> > +It can be used to merge font lock feature lists in a multi-language ma=
jor
> > mode. +
> > ++++
> > +*** New function 'treesit-replace-font-lock-feature-settings'.
> > +Given two treesit-font-lock-settings replaces the feature in the second
> > +font-lock-settings with the same feature in the first
> > +font-lock-settings. In a multi-linguage major mode it is sometimes
> > +necessary to replace features from one of the major modes, with others
> > +that are better suited to the new multilingual context.
> > +
> > ++++
> > +*** New function 'treesit-modify-indent-rules'.
> > +Given two treesit ident rules, it replaces, adds, or prepends the new
> > +rules to the old ones, then returns a new treesit indent rules.
> > +In a multi-linguage major mode it is sometimes necessary to modify rul=
es
> > +from one of the major modes, with others that are better suited to the
> > +new multilingual context.
> > +
> >=20
> >  +++
> >  *** New command 'treesit-explore'
> >  This command replaces 'treesit-explore-mode'.  It turns on
> >=20
> > diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
> > index 101b882c718..be9d6f64aef 100644
> > --- a/lisp/progmodes/js.el
> > +++ b/lisp/progmodes/js.el
> > @@ -3901,6 +3901,44 @@ js--treesit-list-nodes
> >=20
> >  (defvar js--treesit-jsdoc-beginning-regexp (rx bos "/**")
> > =20
> >    "Regular expression matching the beginning of a jsdoc block comment.=
")
> >=20
> > +(defvar js--treesit-thing-settings
> > +  `((javascript
> > +     (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
> > +     (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
> > +     (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
> > +     (text ,(js--regexp-opt-symbol '("comment"
> > +                                     "string_fragment")))))
> > +  "Settings for `treesit-thing-settings'.")
> > +
> > +(defvar js--treesit-font-lock-feature-list
> > +  '(( comment document definition)
> > +    ( keyword string)
> > +    ( assignment constant escape-sequence jsx number
> > +      pattern string-interpolation)
> > +    ( bracket delimiter function operator property))
> > +  "Settings for `treesit-font-lock-feature-list'.")
> > +
> > +(defvar js--treesit-simple-imenu-settings
> > +  `(("Function" "\\`function_declaration\\'" nil nil)
> > +    ("Variable" "\\`lexical_declaration\\'"
> > +     js--treesit-valid-imenu-entry nil)
> > +    ("Class" ,(rx bos (or "class_declaration"
> > +                          "method_definition")
> > +                  eos)
> > +     nil nil))
> > +  "Settings for `treesit-simple-imenu'.")
> > +
> > +(defvar js--treesit-defun-type-regexp
> > +  (rx (or "class_declaration"
> > +          "method_definition"
> > +          "function_declaration"
> > +          "lexical_declaration"))
> > +  "Settings for `treesit-defun-type-regexp'.")
> > +
> > +(defvar js--treesit-jsdoc-comment-regexp
> > +  (rx (or "comment" "line_comment" "block_comment" "description"))
> > +  "Regexp for `c-ts-common--comment-regexp'.")
> > +
> >=20
> >  ;;;###autoload
> >  (define-derived-mode js-ts-mode js-base-mode "JavaScript"
> > =20
> >    "Major mode for editing JavaScript.
> >=20
> > @@ -3931,29 +3969,15 @@ js-ts-mode
> >=20
> >      ;; Indent.
> >      (setq-local treesit-simple-indent-rules js--treesit-indent-rules)
> >      ;; Navigation.
> >=20
> > -    (setq-local treesit-defun-type-regexp
> > -                (rx (or "class_declaration"
> > -                        "method_definition"
> > -                        "function_declaration"
> > -                        "lexical_declaration")))
> > +    (setq-local treesit-defun-type-regexp js--treesit-defun-type-regex=
p)
> > +
> >=20
> >      (setq-local treesit-defun-name-function #'js--treesit-defun-name)
> >=20
> > -    (setq-local treesit-thing-settings
> > -                `((javascript
> > -                   (sexp ,(js--regexp-opt-symbol js--treesit-sexp-node=
s))
> > -                   (list ,(js--regexp-opt-symbol js--treesit-list-node=
s))
> > -                   (sentence ,(js--regexp-opt-symbol
> > js--treesit-sentence-nodes)) -                   (text
> > ,(js--regexp-opt-symbol '("comment"
> > -                                                 =20
> > "string_fragment"))))))
> > +    (setq-local treesit-thing-settings js--treesit-thing-settings)
> >=20
> >      ;; Fontification.
> >      (setq-local treesit-font-lock-settings
> >      js--treesit-font-lock-settings)
> >=20
> > -    (setq-local treesit-font-lock-feature-list
> > -                '(( comment document definition)
> > -                  ( keyword string)
> > -                  ( assignment constant escape-sequence jsx number
> > -                    pattern string-interpolation)
> > -                  ( bracket delimiter function operator property)))
> > +    (setq-local treesit-font-lock-feature-list
> > js--treesit-font-lock-feature-list)>=20
> >      (when (treesit-ready-p 'jsdoc t)
> >     =20
> >        (setq-local treesit-range-settings
> >=20
> > @@ -3963,17 +3987,11 @@ js-ts-mode
> >=20
> >                     :local t
> >                    =20
> >                     `(((comment) @capture (:match
> >                     ,js--treesit-jsdoc-beginning-regexp @capture)))))>=
=20
> > -      (setq c-ts-common--comment-regexp (rx (or "comment" "line_commen=
t"
> > "block_comment" "description")))) +      (setq
> > c-ts-common--comment-regexp js--treesit-jsdoc-comment-regexp))>=20
> >      ;; Imenu
> >=20
> > -    (setq-local treesit-simple-imenu-settings
> > -                `(("Function" "\\`function_declaration\\'" nil nil)
> > -                  ("Variable" "\\`lexical_declaration\\'"
> > -                   js--treesit-valid-imenu-entry nil)
> > -                  ("Class" ,(rx bos (or "class_declaration"
> > -                                        "method_definition")
> > -                                eos)
> > -                   nil nil)))
> > +    (setq-local treesit-simple-imenu-settings
> > js--treesit-simple-imenu-settings) +
> >=20
> >      (treesit-major-mode-setup)
> >     =20
> >      (add-to-list 'auto-mode-alist
> >=20
> > diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
> > index 53340195386..35c61e4f66d 100644
> > --- a/lisp/textmodes/css-mode.el
> > +++ b/lisp/textmodes/css-mode.el
> > @@ -893,13 +893,7 @@ css-mode-syntax-table
> >=20
> >      (modify-syntax-entry ?? "." st)
> >      st))
> >=20
> > -(defvar-keymap css-mode-map
> > -  :doc "Keymap used in `css-mode'."
> > -  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
> > -  ;; `info-complete-symbol' is not used.
> > -  "<remap> <complete-symbol>" #'completion-at-point
> > -  "C-c C-f" #'css-cycle-color-format
> > -  :menu
> > +(defvar css-mode--menu
> >=20
> >    '("CSS"
> >   =20
> >      :help "CSS-specific features"
> >     =20
> >      ["Reformat block" fill-paragraph
> >=20
> > @@ -910,7 +904,17 @@ css-mode-map
> >=20
> >      ["Describe symbol" css-lookup-symbol
> >     =20
> >       :help "Display documentation for a CSS symbol"]
> >     =20
> >      ["Complete symbol" completion-at-point
> >=20
> > -     :help "Complete symbol before point"]))
> > +     :help "Complete symbol before point"])
> > +    "Menu bar for `css-mode'")
> > +
> > +(defvar-keymap css-mode-map
> > +  :doc "Keymap used in `css-mode'."
> > +  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
> > +  ;; `info-complete-symbol' is not used.
> > +  "<remap> <complete-symbol>" #'completion-at-point
> > +  "C-c C-f" #'css-cycle-color-format
> > +  :menu
> > +  css-mode--menu)
> >=20
> >  (eval-and-compile
> > =20
> >    (defconst css--uri-re
> >=20
> > @@ -1771,6 +1775,21 @@ css--extract-index-name
> >=20
> >                (replace-regexp-in-string "[\n ]+" " " s)))
> >            =20
> >             res)))))))
> >=20
> > +(defvar css--treesit-font-lock-feature-list
> > +  '((selector comment query keyword)
> > +    (property constant string)
> > +    (error variable function operator bracket))
> > +  "Settings for `treesit-font-lock-feature-list'.")
> > +
> > +(defvar css--treesit-simple-imenu-settings
> > +  `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
> > +      nil nil))
> > +  "Settings for `treesit-simple-imenu'.")
> > +
> > +(defvar css--treesit-defun-type-regexp
> > +  "rule_set"
> > +  "Settings for `treesit-defun-type-regexp'.")
> > +
> >=20
> >  (define-derived-mode css-base-mode prog-mode "CSS"
> > =20
> >    "Generic mode to edit Cascading Style Sheets (CSS).
> >=20
> > @@ -1825,16 +1844,12 @@ css-ts-mode
> >=20
> >      ;; Tree-sitter specific setup.
> >      (setq treesit-primary-parser (treesit-parser-create 'css))
> >      (setq-local treesit-simple-indent-rules css--treesit-indent-rules)
> >=20
> > -    (setq-local treesit-defun-type-regexp "rule_set")
> > +    (setq-local treesit-defun-type-regexp css--treesit-defun-type-rege=
xp)
> >=20
> >      (setq-local treesit-defun-name-function #'css--treesit-defun-name)
> >      (setq-local treesit-font-lock-settings css--treesit-settings)
> >=20
> > -    (setq-local treesit-font-lock-feature-list
> > -                '((selector comment query keyword)
> > -                  (property constant string)
> > -                  (error variable function operator bracket)))
> > -    (setq-local treesit-simple-imenu-settings
> > -                `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
> > -                    nil nil)))
> > +    (setq-local treesit-font-lock-feature-list
> > css--treesit-font-lock-feature-list) +    (setq-local
> > treesit-simple-imenu-settings css--treesit-simple-imenu-settings) +
> >=20
> >      (treesit-major-mode-setup)
> >     =20
> >      (add-to-list 'auto-mode-alist '("\\.css\\'" . css-ts-mode))))
> >=20
> > diff --git a/lisp/textmodes/html-ts-mode.el
> > b/lisp/textmodes/html-ts-mode.el index dad49b7ed4c..5700c2b406b 100644
> > --- a/lisp/textmodes/html-ts-mode.el
> > +++ b/lisp/textmodes/html-ts-mode.el
> > @@ -87,6 +87,31 @@ html-ts-mode--font-lock-settings
> >=20
> >     `((attribute_name) @font-lock-variable-name-face))
> >   =20
> >    "Tree-sitter font-lock settings for `html-ts-mode'.")
> >=20
> > +(defvar html-ts-mode--treesit-things-settings
> > +  `((html
> > +     (sexp ,(regexp-opt '("element"
> > +                          "text"
> > +                          "attribute"
> > +                          "value")))
> > +     (list ,(regexp-opt '("element")) 'symbols)
> > +     (sentence "tag")
> > +     (text ,(regexp-opt '("comment" "text")))))
> > +  "Settings for `treesit-thing-settings'.")
> > +
> > +(defvar html-ts-mode--treesit-font-lock-feature-list
> > +  '((comment keyword definition)
> > +    (property string)
> > +    () ())
> > +  "Settings for `treesit-font-lock-feature-list'.")
> > +
> > +(defvar html-ts-mode--treesit-simple-imenu-settings
> > +  '(("Element" "\\`tag_name\\'" nil nil))
> > +  "Settings for `treesit-simple-imenu'.")
> > +
> > +(defvar html-ts-mode--treesit-defun-type-regexp
> > +  "element"
> > +  "Settings for `treesit-defun-type-regexp'.")
> > +
> >=20
> >  (defun html-ts-mode--defun-name (node)
> > =20
> >    "Return the defun name of NODE.
> > =20
> >  Return nil if there is no name or if NODE is not a defun node."
> >=20
> > @@ -107,30 +132,18 @@ html-ts-mode
> >=20
> >    (setq-local treesit-simple-indent-rules html-ts-mode--indent-rules)
> >   =20
> >    ;; Navigation.
> >=20
> > -  (setq-local treesit-defun-type-regexp "element")
> > +  (setq-local treesit-defun-type-regexp
> > html-ts-mode--treesit-defun-type-regexp)>=20
> >    (setq-local treesit-defun-name-function #'html-ts-mode--defun-name)
> >=20
> > -  (setq-local treesit-thing-settings
> > -              `((html
> > -                 (sexp ,(regexp-opt '("element"
> > -                                      "text"
> > -                                      "attribute"
> > -                                      "value")))
> > -                 (list ,(regexp-opt '("element")) 'symbols)
> > -                 (sentence "tag")
> > -                 (text ,(regexp-opt '("comment" "text"))))))
> > +  (setq-local treesit-thing-settings
> > html-ts-mode--treesit-things-settings)>=20
> >    ;; Font-lock.
> >    (setq-local treesit-font-lock-settings
> >    html-ts-mode--font-lock-settings)
> >=20
> > -  (setq-local treesit-font-lock-feature-list
> > -              '((comment keyword definition)
> > -                (property string)
> > -                () ()))
> > +  (setq-local treesit-font-lock-feature-list
> > html-ts-mode--treesit-font-lock-feature-list)>=20
> >    ;; Imenu.
> >=20
> > -  (setq-local treesit-simple-imenu-settings
> > -              '(("Element" "\\`tag_name\\'" nil nil)))
> > +  (setq-local treesit-simple-imenu-settings
> > html-ts-mode--treesit-simple-imenu-settings)>=20
> >    ;; Outline minor mode.
> >    (setq-local treesit-outline-predicate "\\`element\\'")
> >=20
> > diff --git a/lisp/textmodes/mhtml-ts-mode.el
> > b/lisp/textmodes/mhtml-ts-mode.el new file mode 100644
> > index 00000000000..272d00a1ef6
> > --- /dev/null
> > +++ b/lisp/textmodes/mhtml-ts-mode.el
> > @@ -0,0 +1,600 @@
> > +;;; mhtml-ts-mode.el --- Major mode for HTML using tree-sitter -*-
> > lexical-binding: t; -*- +
> > +;; Copyright (C) 2024 Free Software Foundation, Inc.
> > +
> > +;; Author: Vincenzo Pupillo <v.pupillo@HIDDEN>
> > +;; Maintainer: Vincenzo Pupillo <v.pupillo@HIDDEN>
> > +;; Created: Nov 2024
> > +;; Keywords: HTML languages hypermedia tree-sitter
> > +
> > +;; This file is part of GNU Emacs.
> > +
> > +;; GNU Emacs is free software: you can redistribute it and/or modify
> > +;; it under the terms of the GNU General Public License as published by
> > +;; the Free Software Foundation, either version 3 of the License, or
> > +;; (at your option) any later version.
> > +
> > +;; GNU Emacs is distributed in the hope that it will be useful,
> > +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > +;; GNU General Public License for more details.
> > +
> > +;; You should have received a copy of the GNU General Public License
> > +;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
> > +
> > +;;; Commentary:
> > +;;
> > +;; This package provides `mhtml-ts-mode' which is a major mode
> > +;; for editing HTML files with embedded JavaScript and CSS.
> > +;; Tree Sitter is used to parse each of these languages.
> > +;;
> > +;; Please note that this package requires `html-ts-mode', which
> > +;; registers itself as the major mode for editing HTML.
> > +;;
> > +;; This package is compatible and has been tested with the following
> > +;; tree-sitter grammars:
> > +;; * https://github.com/tree-sitter/tree-sitter-html
> > +;; * https://github.com/tree-sitter/tree-sitter-javascript
> > +;; * https://github.com/tree-sitter/tree-sitter-jsdoc
> > +;; * https://github.com/tree-sitter/tree-sitter-css
> > +;;
> > +;; Features
> > +;;
> > +;; * Indent
> > +;; * Flymake
> > +;; * IMenu
> > +;; * Navigation
> > +;; * Which-function
> > +;; * Tree-sitter parser installation helper
> > +
> > +;;; Code:
> > +
> > +(require 'treesit)
> > +(require 'html-ts-mode)
> > +(require 'css-mode) ;; for embed css into html
> > +(require 'js) ;; for embed javascript into html
> > +
> > +(eval-when-compile
> > +  (require 'rx))
> > +
> > +;; This tells the byte-compiler where the functions are defined.
> > +;; Is only needed when a file needs to be able to byte-compile
> > +;; in a Emacs not built with tree-sitter library.
> > +(treesit-declare-unavailable-functions)
> > +
> > +;; In a multi-language major mode can be useful to have an "installer"=
 to
> > +;; simplify the installation of the grammars supported by the major-mo=
de.
> > +(defvar mhtml-ts-mode--language-source-alist
> > +  '((html . ("https://github.com/tree-sitter/tree-sitter-html"=20
> > "v0.23.2")) +    (javascript .
> > ("https://github.com/tree-sitter/tree-sitter-javascript" "v0.23.1")) + =
 =20
> > (jsdoc . ("https://github.com/tree-sitter/tree-sitter-jsdoc" "v0.23.2"))
> > +    (css . ("https://github.com/tree-sitter/tree-sitter-css"
> > "v0.23.1"))) +  "Treesitter language parsers required by `mhtml-ts-mode=
'.
> > +You can customize this variable if you want to stick to a specific
> > +commit and/or use different parsers.")
> > +
> > +(defun mhtml-ts-mode-install-parsers ()
> > +  "Install all the required treesitter parsers.
> > +`mhtml-ts-mode--language-source-alist' defines which parsers to instal=
l."
> > +  (interactive)
> > +  (let ((treesit-language-source-alist
> > mhtml-ts-mode--language-source-alist)) +    (dolist (item
> > mhtml-ts-mode--language-source-alist)
> > +      (treesit-install-language-grammar (car item)))))
> > +
> > +;;; Custom variables
> > +
> > +(defgroup mhtml-ts-mode nil
> > +  "Major mode for editing HTML files, based on `html-ts-mode'.
> > +Works with JS and CSS and for that use `js-ts-mode' and `css-ts-mode'."
> > +  :prefix "mhtml-ts-mode-"
> > +  ;; :group 'languages
> > +  :group 'html)
> > +
> > +(defcustom mhtml-ts-mode-js-css-indent-offset 2
> > +  "JavaScript and CSS indent spaces related to the <script> and <style>
> > HTML tags. +By default should have same value as
> > `html-ts-mode-indent-offset'." +  :tag "HTML javascript or css indent
> > offset"
> > +  :version "31.1"
> > +  :type 'integer
> > +  :safe 'integerp)
> > +
> > +(defcustom mhtml-ts-mode-pretty-print-command
> > +  ;; prefer tidy because it's used by sgml-mode
> > +  (let ((executable nil))
> > +    (cond ((setq executable (executable-find "tidy"))
> > +           (format
> > +            "%s --gnu-emacs yes --wrap 0 --indent-spaces %s -q -i -"
> > +            executable html-ts-mode-indent-offset))
> > +          ((setq executable (executable-find "xmllint"))
> > +           (format "%s --html --quiet --format -" executable))
> > +          (t "Install tidy, ore some other HTML pretty print tool, and
> > set `mhtml-ts-mode-pretty-print-command'."))) +  "The command to pretty
> > print the current HTML buffer."
> > +  :type 'string
> > +  :version "31.1")
> > +
> > +(defvar mhtml-ts-mode--js-css-indent-offset
> > +  mhtml-ts-mode-js-css-indent-offset
> > +  "Internal copy of `mhtml-ts-mode-js-css-indent-offset'.
> > +The value changes, by `mhtml-ts-mode--tag-relative-indent-offset'
> > according to +the value of `mhtml-ts-mode-tag-relative-indent'.")
> > +
> > +(defun mhtml-ts-mode--tag-relative-indent-offset (sym val)
> > +  "Custom setter for `mhtml-ts-mode-tag-relative-indent'.
> > +Apart from setting the default value of SYM to VAL, also change the
> > +value of SYM in `mhtml-ts-mode' buffers to VAL.  SYM should be
> > +`mhtml-ts-mode-tag-relative-indent', and VAL should be t, nil or
> > +`ignore'.  When sym is `mhtml-ts-mode-tag-relative-indent' set the
> > +value of `mhtml-ts-mode--js-css-indent-offset' to 0 if VAL is t,
> > +otherwise to `mhtml-ts-mode-js-css-indent-offset'."
> > +  (set-default sym val)
> > +  (when (eq sym 'mhtml-ts-mode-tag-relative-indent)
> > +    (setq
> > +     mhtml-ts-mode--js-css-indent-offset
> > +     (if (eq val t)
> > +         mhtml-ts-mode-js-css-indent-offset
> > +       0))))
> > +
> > +(defcustom mhtml-ts-mode-tag-relative-indent t
> > +  "How <script> and <style> bodies are indented relative to the tag.
> > +
> > +When t, indentation looks like:
> > +
> > +  <script>
> > +    code();
> > +  </script>
> > +
> > +When nil, indentation of the tag body starts just below the
> > +tag, like:
> > +
> > +  <script>
> > +  code();
> > +  </script>
> > +
> > +When `ignore', the tag body starts in the first column, like:
> > +
> > +  <script>
> > +code();
> > +  </script>"
> > +  :type '(choice (const nil) (const t) (const ignore))
> > +  :safe 'symbolp
> > +  :set #'mhtml-ts-mode--tag-relative-indent-offset
> > +  :version "31.1")
> > +
> > +(defcustom mhtml-ts-mode-css-fontify-colors t
> > +  "Whether CSS colors should be fontified using the color as the
> > background. +If non-nil, text representing a CSS color will be fontified
> > +such that its background is the color itself.
> > +Works like `css--fontify-region'."
> > +  :tag "HTML colors the CSS properties values."
> > +  :version "31.1"
> > +  :type 'boolean
> > +  :safe 'booleanp)
> > +
> > +(defvar mhtml-ts-mode-saved-pretty-print-command nil
> > +  "The command last used to pretty print in this buffer.")
> > +
> > +(defun mhtml-ts-mode-pretty-print (command)
> > +  "Prettify the current buffer.
> > +Argument COMMAND The command to use."
> > +  (interactive
> > +   (list (read-string
> > +          "Prettify command: "
> > +          (or mhtml-ts-mode-saved-pretty-print-command
> > +              (concat mhtml-ts-mode-pretty-print-command " ")))))
> > +  (setq mhtml-ts-mode-saved-pretty-print-command command)
> > +  (save-excursion
> > +    (shell-command-on-region
> > +     (point-min) (point-max)
> > +     command (buffer-name) t
> > +     "*mhtml-ts-mode-pretty-pretty-print-errors*" t)))
> > +
> > +(defun mhtml-ts-mode--switch-fill-defun (&rest arguments)
> > +  "Switch between `fill-paragraph' and `prog-fill-reindent-defun'.
> > +In an HTML region it calls `fill-paragraph' as does `html-ts-mode',
> > +otherwise it calls `prog-fill-reindent-defun'.
> > +Optional ARGUMENTS to to be passed to it."
> > +  (interactive)
> > +  (if (eq (treesit-language-at (point)) 'html)
> > +      (funcall-interactively #'fill-paragraph arguments)
> > +    (funcall-interactively #'prog-fill-reindent-defun arguments)))
> > +
> > +(defvar-keymap mhtml-ts-mode-map
> > +  :doc "Keymap for `mhtml-ts-mode' buffers."
> > +  :parent html-mode-map
> > +  ;; `mhtml-ts-mode' derive from `html-ts-mode' so the keymap is the
> > +  ;; same, we need to add some mapping from others languages.
> > +  "C-c C-f" #'css-cycle-color-format
> > +  "M-q" #'mhtml-ts-mode--switch-fill-defun)
> > +
> > +;; Place the CSS menu in the menu bar as well.
> > +(easy-menu-define mhtml-ts-mode-menu mhtml-ts-mode-map
> > +  "Menu bar for `mhtml-ts-mode'."
> > +  css-mode--menu)
> > +
> > +;; Not used at the moment.
> > +(defun mthml-ts-mode--js-language-at-point (point)
> > +  "Return the language at POINT assuming the point is within a Javascr=
ipt
> > region." +  (let* ((node (treesit-node-at point 'javascript))
> > +         (node-type (treesit-node-type node))
> > +         (node-start (treesit-node-start node))
> > +         (node-end (treesit-node-end node)))
> > +    (if (not (treesit-ready-p 'jsdoc t))
> > +        'javascript
> > +      (if (equal node-type "comment")
> > +          (save-excursion
> > +            ;; (message "node start =3D %s , end =3D %s" node-start no=
de-end)
> > +            (goto-char node-start)
> > +            (if (search-forward "/**" node-end t)
> > +                'jsdoc
> > +              'javascript))
> > +        'javascript))))
> > +
> > +;; To enable some basic treesiter functionality, you should define
> > +;; a function that recognizes which grammar is used at-point.
> > +;; This function should be assigned to
> > `treesit-language-at-point-function' +(defun
> > mhtml-ts-mode--language-at-point (point)
> > +  "Return the language at POINT assuming the point is within a HTML
> > buffer." +  (let* ((node (treesit-node-at point 'html))
> > +         (parent (treesit-node-parent node))
> > +         (node-query (format "(%s (%s))"
> > +                             (treesit-node-type parent)
> > +                             (treesit-node-type node))))
> > +    (cond
> > +     ((equal "(script_element (raw_text))" node-query) 'javascript)
> > +     ;; ((equal "(script_element (raw_text))" node-query)
> > 'mthml-ts-mode--js-language-at-point) +     ((equal "(style_element
> > (raw_text))" node-query) 'css)
> > +     (t 'html))))
> > +
> > +;; Custom font-lock function that's used to apply color to css color
> > +;; The signature of the function should be conforming to signature
> > +;; QUERY-SPEC required by `treesit-font-lock-rules'.
> > +(defun mhtml-ts-mode--colorize-css-value (node override start end &rest
> > _)
> > +  "Colorize CSS property value like `css--fontify-region'.
> > +For NODE, OVERRIDE, START, and END, see `treesit-font-lock-rules'."
> > +  (if (and mhtml-ts-mode-css-fontify-colors
> > +           (string-equal "plain_value" (treesit-node-type node)))
> > +      (let ((color (css--compute-color start (treesit-node-text node
> > t))))
> > +        (when color
> > +          (with-silent-modifications
> > +            (add-text-properties
> > +             (treesit-node-start node) (treesit-node-end node)
> > +             (list 'face (list :background color
> > +                               :foreground (readable-foreground-color
> > +                                            color)
> > +                               :box '(:line-width -1)))))))
> > +    (treesit-fontify-with-override
> > +     (treesit-node-start node) (treesit-node-end node)
> > +     'font-lock-variable-name-face
> > +     override start end)))
> > +
> > +;; Embedded languages =E2=80=8B=E2=80=8Bshould be indented according t=
o the language
> > +;; that embeds them.
> > +;; This function signature complies with `treesit-simple-indent-rules'
> > +;; ANCHOR.
> > +(defun mhtml-ts-mode--js-css-tag-bol (_node _parent &rest _)
> > +  "Find the first non-space characters of html tags <script> or <style=
>.
> > +Return `line-beginning-position' when `treesit-node-at' is html, or
> > +`mhtml-ts-mode-tag-relative-indent' is equal to ignore.
> > +NODE and PARENT are ignored."
> > +  (if (or (eq (treesit-language-at (point)) 'html)
> > +          (eq mhtml-ts-mode-tag-relative-indent 'ignore))
> > +      (line-beginning-position)
> > +    ;; Ok, we are in js or css block.
> > +    (save-excursion
> > +      (re-search-backward "<script.*>\\|<style.*>" nil t))))
> > +
> > +;; Treesit supports 4 level of decoration, `treesit-font-lock-level'
> > +;; define which level to use.  Major modes categorize their fontificat=
ion
> > +;; features, these categories are defined by `treesit-font-lock-rules'=
 of
> > +;; each major-mode using :feature keyword.
> > +;; In a multiple language Major mode it's a good idea to provide, for
> > each
> > +;; level, the union of the :feature of the same level.
> > +;; TODO: Since the feature-list is not defined per "parser" (like, for
> > +;; example, the thing-settings), the same feature can appear in
> > +;; different levels, so the appearance of a multiple main mode can be
> > +;; different from the main mode used.  For e.g the feature "function" =
is
> > +;; at level 4 for Javascript while it is at level 3 for CSS.
> > +(defvar mhtml-ts-mode--treesit-font-lock-feature-list
> > +  (treesit-merge-font-lock-feature-list
> > +   html-ts-mode--treesit-font-lock-feature-list
> > +   (treesit-merge-font-lock-feature-list
> > +    js--treesit-font-lock-feature-list
> > +    css--treesit-font-lock-feature-list))
> > +  "Settings for `treesit-font-lock-feature-list'.")
> > +
> > +(defvar mhtml-ts-mode--treesit-font-lock-settings
> > +  (append html-ts-mode--font-lock-settings
> > +          js--treesit-font-lock-settings
> > +          ;; Let's replace a css rule with a new one that adds color to
> > +          ;; the css value.
> > +          (treesit-replace-font-lock-feature-settings
> > +           (treesit-font-lock-rules
> > +            :language 'css
> > +            :override t
> > +            :feature 'variable
> > +            '((plain_value) @font-lock-variable-name-face
> > +              (plain_value) @mhtml-ts-mode--colorize-css-value))
> > +           css--treesit-settings))
> > +  "Settings for `treesit-font-lock-settings'.")
> > +
> > +(defvar mhtml-ts-mode--treesit-thing-settings
> > +  ;; In addition to putting together the various definitions, we need =
to
> > +  ;; add 'defun' which is used to support `imenu' and 'which-function'.
> > +  (list
> > +   ;; HTML thing settings
> > +   (append
> > +    (car html-ts-mode--treesit-things-settings)
> > +    `((defun ,(regexp-opt (list
> > html-ts-mode--treesit-defun-type-regexp))))) +   ;; Javascript thing
> > settings
> > +   (append
> > +    (car js--treesit-thing-settings)
> > +    `((defun ,js--treesit-defun-type-regexp)))
> > +   ;; CSS thing settings
> > +   `(css
> > +     (defun ,(regexp-opt (list css--treesit-defun-type-regexp)))))
> > +  "Settings for `treesit-thing-settings'.")
> > +
> > +(defvar mhtml-ts-mode--treesit-indent-rules
> > +  (treesit--indent-rules-optimize
> > +   (append html-ts-mode--indent-rules
> > +           ;; Extended rules for js and css, to
> > +           ;; indent appropriately when injected
> > +           ;; into html
> > +           (treesit-modify-indent-rules
> > +            `((javascript ((parent-is "program")
> > +                           mhtml-ts-mode--js-css-tag-bol
> > +                           mhtml-ts-mode--js-css-indent-offset)))
> > +            js--treesit-indent-rules
> > +            :replace)
> > +           (treesit-modify-indent-rules
> > +            `((css ((parent-is "stylesheet")
> > +                    mhtml-ts-mode--js-css-tag-bol
> > +                    mhtml-ts-mode--js-css-indent-offset)))
> > +            css--treesit-indent-rules 'prepend)
> > +           :replace))
> > +  "Settings for `treesit-simple-indent-rules'.")
> > +
> > +(defvar mhtml-ts-mode--treesit-aggregated-simple-imenu-settings
> > +  `((html ,@html-ts-mode--treesit-simple-imenu-settings)
> > +    (javascript ,@js--treesit-simple-imenu-settings)
> > +    (css ,@css--treesit-simple-imenu-settings))
> > +  "Settings for `treesit-simple-imenu'.")
> > +
> > +;; TODO: treesit-defun-type-regexp should have an aggregated version,
> > +;; like treesit-aggregated-simple-imenu-settings. Otherwise we can't
> > +;; reuse the regex defined in the major mode we use.
> > +(defvar mhtml-ts-mode--treesit-defun-type-regexp
> > +  (regexp-opt '("class_declaration"
> > +                "method_definition"
> > +                "function_declaration"
> > +                "lexical_declaration"
> > +                "element"
> > +                "rule_set"))
> > +  "Settings for `treesit-defun-type-regexp'.")
> > +
> > +;; In order to support `prettify-symbols-mode', just `append' the
> > prettify
> > +;; alist of all the languages. In our case only javascript defined this
> > alist. +(defvar mhtml-ts-mode--prettify-symbols-alist
> > js--prettify-symbols-alist +  "Alist of symbol prettifications for
> > various supported languages.") +
> > +;; In order to support `which-fuction-mode' we should define
> > +;; a function that return the defun name.
> > +;; In a multilingual treesit mode, this can be implemented simply by
> > +;; calling language-specific functions.
> > +(defun mhtml-ts-mode--defun-name (node)
> > +  "Return the defun name of NODE.
> > +Return nil if there is no name or if NODE is not a defun node."
> > +    (let ((html-name (html-ts-mode--defun-name node))
> > +          (js-name (js--treesit-defun-name node))
> > +          (css-name (css--treesit-defun-name node)))
> > +      (cond
> > +       (html-name html-name)
> > +       (js-name js-name)
> > +       (css-name css-name))))
> > +
> > +;;; Flymake integration
> > +
> > +(defvar-local mhtml-ts-mode--flymake-process nil
> > +  "Store the Flymake process.")
> > +
> > +(defun mhtml-ts-mode-flymake-mhtml (report-fn &rest _args)
> > +  "MHTML backend for Flymake.
> > +Calls REPORT-FN directly.  Requires tidy."
> > +  (when (process-live-p mhtml-ts-mode--flymake-process)
> > +    (kill-process mhtml-ts-mode--flymake-process))
> > +  (let ((tidy (executable-find "tidy"))
> > +        (source (current-buffer))
> > +        (diagnostics-pattern (eval-when-compile
> > +                               (rx bol
> > +                                   "line " (group (+ num))    ;; :1 li=
ne
> > +                                   " column " (group (+ num)) ;; :2
> > column
> > +                                   " - " (group (+? nonl))    ;; :3 ty=
pe
> > +                                   ": " (group (+? nonl))     ;; :4 msg
> > +                                   eol))))
> > +    (if (not tidy)
> > +        (error "Unable to find tidy command")
> > +      (save-restriction
> > +        (widen)
> > +        (setq mhtml-ts-mode--flymake-process
> > +              (make-process
> > +               :name "mhtml-ts-mode-flymake"
> > +               :noquery t
> > +               :connection-type 'pipe
> > +               :buffer (generate-new-buffer "*mhtml-ts-mode-flymake*")
> > +               :command `(,tidy "--gnu-emacs" "yes" "-e" "-q")
> > +               :sentinel
> > +               (lambda (proc _event)
> > +                 (when (eq 'exit (process-status proc))
> > +                   (unwind-protect
> > +                       (if (with-current-buffer source
> > +                             (eq proc mhtml-ts-mode--flymake-process))
> > +                           (with-current-buffer (process-buffer proc)
> > +                             (goto-char (point-min))
> > +                             (let (diags)
> > +                               (while (search-forward-regexp
> > diagnostics-pattern nil t) +                                 (let* ((pos
> > +                                         (flymake-diag-region
> > +                                          source
> > +                                          (string-to-number (match-str=
ing
> > 1)) +                                          (string-to-number
> > (match-string 2)))) ;; line and column +                               =
 =20
> >       (type (cond ((equal (match-string 3) "Warning") :warning) +      =
 =20
> >                                            ((equal (match-string 3)
> > "Error") :error))) ;; type of message +                                =
 =20
> >      (msg (match-string 4))) ;; message +                              =
 =20
> >   (push (flymake-make-diagnostic source (car pos) (cdr pos) type msg) +=
 =20
> >                                       diags)))
> > +                               (funcall report-fn diags)))
> > +                         (flymake-log :warning "Canceling obsolete che=
ck
> > %s" proc)) +                     (kill-buffer (process-buffer proc)))))=
))
> > +        (process-send-region mhtml-ts-mode--flymake-process (point-min)
> > (point-max)) +        (process-send-eof
> > mhtml-ts-mode--flymake-process)))))
> > +
> > +(define-derived-mode mhtml-ts-mode html-ts-mode
> > +  '("HTML+" (:eval (let ((lang (mhtml-ts-mode--language-at-point
> > (point)))) +                     (cond ((eq lang 'html) "")
> > +                           ((eq lang 'javascript) "JS")
> > +                           ((eq lang 'css) "CSS")))))
> > +  "Major mode for editing HTML with embedded JavaScript and CSS.
> > +Powered by tree-sitter."
> > +  (if (not (and
> > +            (treesit-ready-p 'html)
> > +            (treesit-ready-p 'javascript)
> > +            (treesit-ready-p 'css)))
> > +      (error "Tree-sitter parsers for HTML isn't available.  You can
> > +    install the parsers with M-x `mhtml-ts-mode-install-parsers'")
> > +
> > +    ;; When an language is embedded, you should initialize some variab=
le
> > +    ;; just like it's done in the original mode.
> > +
> > +    ;; Comment.
> > +    ;; indenting settings for js-ts-mode.
> > +    (c-ts-common-comment-setup)
> > +    (setq-local comment-multi-line t)
> > +
> > +    ;; Font-lock.
> > +
> > +    ;; There are two ways to handle embedded code:
> > +    ;; 1. Use a single parser for all the embedded code in the buffer.=
 In
> > +    ;; this case, the embedded code blocks are concatenated together a=
nd
> > are +    ;; seen as a single continuous document to the parser.
> > +    ;; 2. Each embedded code block gets its own parser. Each parser on=
ly
> > sees +    ;; that particular code block.
> > +
> > +    ;; If you go with 2 for a language, the local parsers are created =
and
> > +    ;; destroyed automatically by Emacs. So don't create a global pars=
er
> > for +    ;; that embedded language here.
> > +
> > +    ;; Create the parsers, only the global ones.
> > +    ;; jsdoc is a local parser, don't create a parser for it.
> > +    (treesit-parser-create 'css)
> > +    (treesit-parser-create 'javascript)
> > +
> > +    ;; Multi-language modes must set the  primary parser.
> > +    (setq-local treesit-primary-parser (treesit-parser-create 'html))
> > +
> > +    (setq-local treesit-range-settings
> > +                (treesit-range-rules
> > +                 :embed 'javascript
> > +                 :host 'html
> > +                 '((script_element
> > +                    (start_tag (tag_name))
> > +                    (raw_text) @cap))
> > +
> > +                 ;; Another rule could be added that when it matches an
> > +                 ;; attribute_value that has as its parent an
> > +                 ;; attribute_name "style" it captures it and then
> > +                 ;; passes it to the css parser.
> > +                 :embed 'css
> > +                 :host 'html
> > +                 '((style_element
> > +                    (start_tag (tag_name))
> > +                    (raw_text) @cap))))
> > +
> > +    ;; jsdoc is not mandatory for js-ts-mode, so we respect this by
> > +    ;; adding jsdoc range rules only when jsdoc is available.
> > +    (when (treesit-ready-p 'jsdoc t)
> > +      (setq-local treesit-range-settings
> > +                  (append treesit-range-settings
> > +                          (treesit-range-rules
> > +                           :embed 'jsdoc
> > +                           :host 'javascript
> > +                           :local t
> > +                           `(((comment) @cap
> > +                              (:match ,js--treesit-jsdoc-beginning-reg=
exp
> > @cap)))))) +      (setq-local c-ts-common--comment-regexp
> > +                  js--treesit-jsdoc-comment-regexp))
> > +
> > +
> > +    ;; Many treesit fuctions need to know the language at-point.
> > +    ;; So you should define such a function.
> > +    (setq-local treesit-language-at-point-function
> > #'mhtml-ts-mode--language-at-point) +    (setq-local
> > prettify-symbols-alist mhtml-ts-mode--prettify-symbols-alist) +
> > +    ;; Indent.
> > +
> > +    ;; Since `mhtml-ts-mode' inherits indentation rules from
> > `html-ts-mode', `js-ts-mode' +    ;; and `css-ts-mode', if you want to
> > change the offset you have to act on the +    ;; *-offset variables
> > defined for those languages.
> > +
> > +    ;; JavaScript and CSS must be indented relative to their code bloc=
k.
> > +    ;; This is done by inserting a special rule before the normal
> > +    ;; indentation rules of these languages.
> > +    ;; The value of `mhtml-ts-mode--js-css-indent-offset' changes based
> > on
> > +    ;; `mhtml-ts-mode-tag-relative-indent' and can be used to indent
> > +    ;; JavaScript and CSS code relative to the HTML that contains them,
> > +    ;; just like in mhtml-mode.
> > +    (setq-local treesit-simple-indent-rules
> > mhtml-ts-mode--treesit-indent-rules) +
> > +    ;; Navigation.
> > +
> > +    ;; This is for which-function-mode.
> > +    ;; Since mhtml-ts-mode is derived from html-ts-mode, which sets
> > +    ;; the value of `treesit-defun-type-regexp', you have to reset it =
to
> > nil +    ;; otherwise `imenu' and `which-function-mode' will not work. =
+=20
> >   (setq-local treesit-defun-type-regexp nil)
> > +
> > +    ;; This is for finding defun name, it's used by IMenu as default
> > +    ;; function no specific functions are defined.
> > +    (setq-local treesit-defun-name-function #'mhtml-ts-mode--defun-nam=
e)
> > +
> > +    ;; Define what are 'thing' for treesit.
> > +    ;; 'Thing' is a symbol representing the thing, like `defun', `sexp=
',
> > or +    ;; `sentence'.
> > +    ;; As an alternative, if you want just defun, you can define a
> > `treesit-defun-type-regexp'. +    (setq-local treesit-thing-settings
> > mhtml-ts-mode--treesit-thing-settings) +
> > +    ;; Font-lock.
> > +
> > +    ;; In a multi-language scenario, font lock settings are usually a
> > +    ;; concatenation of language rules. As you can see, it is possible
> > +    ;; to extend/modify the default rule or use a different set of
> > +    ;; rules. See `php-ts-mode--custom-html-font-lock-settings' for mo=
re
> > +    ;; advanced usage.
> > +    (setq-local treesit-font-lock-settings
> > mhtml-ts-mode--treesit-font-lock-settings) +
> > +    ;; Tells treesit the list of features to fontify.
> > +    (setq-local treesit-font-lock-feature-list
> > mhtml-ts-mode--treesit-font-lock-feature-list) +
> > +    ;; Imenu
> > +
> > +    ;; Setup Imenu: if no function is specified, try to find an object
> > +    ;; using `treesit-defun-name-function'.
> > +    (setq-local treesit-aggregated-simple-imenu-settings
> > +                mhtml-ts-mode--treesit-aggregated-simple-imenu-setting=
s)
> > +
> > +    (treesit-major-mode-setup)
> > +
> > +    ;; This is sort of a prog-mode as well as a text mode.
> > +    (run-mode-hooks 'prog-mode-hook)
> > +
> > +    ;; Flymake
> > +    (add-hook 'flymake-diagnostic-functions #'mhtml-ts-mode-flymake-mh=
tml
> > nil 'local))) +
> > +;; Add nome extra parents.
> > +(derived-mode-add-parents 'mhtml-ts-mode '(css-mode js-mode))
> > +
> > +(when (and (treesit-ready-p 'html) (treesit-ready-p 'javascript)
> > (treesit-ready-p 'css)) +  (add-to-list
> > +   'auto-mode-alist '("\\.[sx]?html?\\(\\.[a-zA-Z_]+\\)?\\'" .
> > mhtml-ts-mode))) +
> > +(provide 'mhtml-ts-mode)
> > +;;; mhtml-ts-mode.el ends here
> > diff --git a/lisp/treesit.el b/lisp/treesit.el
> > index 8d86d142e3f..00b7d266e74 100644
> > --- a/lisp/treesit.el
> > +++ b/lisp/treesit.el
> > @@ -1297,6 +1297,40 @@ treesit-font-lock-recompute-features
> >=20
> >                         ((memq feature remove-list) nil)
> >                         (t current-value))))))
> >=20
> > +(defun treesit-merge-font-lock-feature-list (features-list-1
> > features-list-2) +  "Merge two tree-sitter font lock feature lists.
> > +Returns a new font lock feature list with no duplicates in the same
> > level.
> > +It can be used to merge font lock feature lists in a multi-language ma=
jor
> > mode. +FEATURES-LIST-1 and FEATURES-LIST-2 are list of lists of feature
> > symbols." +    (let ((result nil)
> > +	(features-1 (car features-list-1))
> > +	(features-2 (car features-list-2)))
> > +    (while (or features-1 features-2)
> > +      (cond
> > +       ((and features-1 (not features-2)) (push features-1 result))
> > +       ((and (not features-1) features-2) (push features-2 result))
> > +       ((and features-1 features-2) (push (cl-union features-1
> > features-2) result))) +      (setq features-list-1 (cdr features-list-1)
> > +	    features-list-2 (cdr features-list-2)
> > +	    features-1 (car features-list-1)
> > +            features-2 (car features-list-2)))
> > +    (nreverse result)))
> > +
> > +(defun treesit-replace-font-lock-feature-settings (new-settings settin=
gs)
> > +  "Replaces :feature in SETTINGS with :feature from NEW-SETTINGS.
> > +Both SETTINGS and NEW-SETTINGS must be a value suitable for
> > +`treesit-font-lock-settings'.
> > +Return a value suitable for `treesit-font-lock-settings'"
> > +  (let ((result nil))
> > +    (dolist (new-setting new-settings)
> > +      (let ((new-feature (treesit-font-lock-setting-feature
> > new-setting)))
> > +	(dolist (setting settings)
> > +	  (let ((feature (treesit-font-lock-setting-feature setting)))
> > +	    (if (eq new-feature feature)
> > +		(push new-setting result)
> > +	      (push setting result))))))
> > +    (nreverse result)))
> > +
> >=20
> >  (defun treesit-add-font-lock-rules (rules &optional how feature)
> > =20
> >    "Add font-lock RULES to the current buffer.
> >=20
> > @@ -2401,6 +2435,37 @@ treesit--indent-rules-optimize
> >=20
> >                              offset)))))
> >              =20
> >               (cons lang (mapcar #'optimize-rule indent-rules)))))
> >=20
> > +(defun treesit-modify-indent-rules (new-rules rules &optional how)
> > +  "Modify RULES using NEW-RULES.
> > +As default replace rules with the same anchor.
> > +When HOW is :prepend NEW-RULES are prepend to RULES, when
> > +:append NEW-RULES are appended to RULES, when :replace (the default)
> > +NEW-RULES replace rule in RULES which the same anchor."
> > +  (let ((n-lang (car (car new-rules)))
> > +	(lang (car (car rules))))
> > +    (when (not (eq n-lang lang))
> > +      (error "The language must be the same"))
> > +    (let* ((nr (cdr (car new-rules)))
> > +           (r (cdr (car rules)))
> > +           (tmp nil)
> > +           (result
> > +            (cond
> > +             ((eq how :prepend)
> > +	      (append nr r))
> > +             ((eq how :append)
> > +              (append r nr))
> > +             ((or (eq how :replace) t)
> > +              (nreverse
> > +               (progn
> > +                 (dolist (new-rule nr)
> > +	           (dolist (rule r)
> > +	             (if (equal (nth 0 new-rule) (nth 0 rule))
> > +		         (push new-rule tmp)
> > +		       (push rule tmp))))
> > +                 tmp))))))
> > +      (push lang result)
> > +      `(,result))))
> > +
> >=20
> >  ;;; Search
> > =20
> >  (defun treesit-search-forward-goto


--nextPart26760901.1r3eYUQgxm
Content-Disposition: attachment; filename="0001-Add-mhtml-ts-mode.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="UTF-8";
 name="0001-Add-mhtml-ts-mode.patch"

From 70fa27e4910f10e5b371954f44717ca2fd81d2e1 Mon Sep 17 00:00:00 2001
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
Date: Sun, 9 Feb 2025 15:19:21 +0100
Subject: [PATCH] Add mhtml-ts-mode.

New major-mode alternative to mhtml-mode, based on treesitter, for
editing files containing html, javascript and css.

* etc/NEWS: Mention the new mode and new functions.
* lisp/textmodes/mhtml-ts-mode.el: New file.
* lisp/progmodes/js.el (js--treesit-thing-settings): New variable.
(js--treesit-font-lock-feature-list); New variable.
(js--treesit-simple-imenu-settings): New variable.
(js--treesit-defun-type-regexp): New variable.
(js--treesit-jsdoc-comment-regexp): New variable.
(js-ts-mode): Use of new variables instead of direct assignment of
values.
* lisp/textmodes/css-mode.el (css-mode--menu): New variable.
(css-mode-map): Use new variable.
(css--treesit-font-lock-feature-list): New variable.
(css--treesit-simple-imenu-settings): New variable.
(css--treesit-defun-type-regexp): New variable.
(cs-ts-mode): Use of new variables instead of direct assignment of
values.
* lisp/textmodes/html-ts-mode.el
(html-ts-mode--treesit-things-settings): New variable.
(html-ts-mode--treesit-font-lock-feature-list): New variable.
(html-ts-mode--treesit-simple-imenu-settings): New variable.
(html-ts-mode--treesit-defun-type-regexp): New variable.
(html-ts-mode): Use of new variables instead of direct assignment of
values.
* lisp/treesit.el
(treesit-merge-font-lock-feature-list): New fuction.
(treesit-replace-font-lock-feature-settings): New fuction.
(treesit-modify-indent-rules): New function.
---
 etc/NEWS                       | 29 ++++++++++
 lisp/progmodes/js.el           | 72 +++++++++++++++----------
 lisp/textmodes/css-mode.el     | 47 ++++++++++------
 lisp/textmodes/html-ts-mode.el | 45 ++++++++++------
 lisp/treesit.el                | 99 ++++++++++++++++++++++++++++++++++
 5 files changed, 233 insertions(+), 59 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 4ab24df14d2..41927d1b44d 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1124,6 +1124,12 @@ runs its body, and removes the current buffer from
 
 
 * New Modes and Packages in Emacs 31.1
+** New major modes based on the tree-sitter library
+
+*** New major mode 'mhtml-ts-mode'.
+An optional major mode based on the tree-sitter library for editing html
+files. This mode handles indentation, fontification, and commenting for
+embedded JavaScript and CSS.
 
 
 * Incompatible Lisp Changes in Emacs 31.1
@@ -1268,6 +1274,29 @@ language symbol.  For example, 'cpp' is translated to "C++".  A new
 variable 'treesit-language-display-name-alist' holds the translations of
 language symbols where that translation is not trivial.
 
++++
+++++
+*** New function 'treesit-merge-font-lock-feature-list'.
+This function the merge two tree-sitter font lock feature lists.
+Returns a new font lock feature list with no duplicates in the same level.
+It can be used to merge font lock feature lists in a multi-language major mode.
+
++++
+*** New function 'treesit-replace-font-lock-feature-settings'.
+Given two treesit-font-lock-settings replaces the feature in the second
+font-lock-settings with the same feature in the first
+font-lock-settings. In a multi-linguage major mode it is sometimes
+necessary to replace features from one of the major modes, with others
+that are better suited to the new multilingual context.
+
++++
+*** New function 'treesit-modify-indent-rules'.
+Given two treesit ident rules, it replaces, adds, or prepends the new
+rules to the old ones, then returns a new treesit indent rules.
+In a multi-linguage major mode it is sometimes necessary to modify rules
+from one of the major modes, with others that are better suited to the
+new multilingual context.
+
 +++
 *** New command 'treesit-explore'.
 This command replaces 'treesit-explore-mode'.  It turns on
diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
index 3168395acf1..f2acf9f40d6 100644
--- a/lisp/progmodes/js.el
+++ b/lisp/progmodes/js.el
@@ -3920,6 +3920,44 @@ js--treesit-list-nodes
 (defvar js--treesit-jsdoc-beginning-regexp (rx bos "/**")
   "Regular expression matching the beginning of a jsdoc block comment.")
 
+(defvar js--treesit-thing-settings
+  `((javascript
+     (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
+     (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
+     (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
+     (text ,(js--regexp-opt-symbol '("comment"
+                                     "string_fragment")))))
+  "Settings for `treesit-thing-settings'.")
+
+(defvar js--treesit-font-lock-feature-list
+  '(( comment document definition)
+    ( keyword string)
+    ( assignment constant escape-sequence jsx number
+      pattern string-interpolation)
+    ( bracket delimiter function operator property))
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar js--treesit-simple-imenu-settings
+  `(("Function" "\\`function_declaration\\'" nil nil)
+    ("Variable" "\\`lexical_declaration\\'"
+     js--treesit-valid-imenu-entry nil)
+    ("Class" ,(rx bos (or "class_declaration"
+                          "method_definition")
+                  eos)
+     nil nil))
+  "Settings for `treesit-simple-imenu'.")
+
+(defvar js--treesit-defun-type-regexp
+  (rx (or "class_declaration"
+          "method_definition"
+          "function_declaration"
+          "lexical_declaration"))
+  "Settings for `treesit-defun-type-regexp'.")
+
+(defvar js--treesit-jsdoc-comment-regexp
+  (rx (or "comment" "line_comment" "block_comment" "description"))
+  "Regexp for `c-ts-common--comment-regexp'.")
+
 ;;;###autoload
 (define-derived-mode js-ts-mode js-base-mode "JavaScript"
   "Major mode for editing JavaScript.
@@ -3951,29 +3989,15 @@ js-ts-mode
     ;; Indent.
     (setq-local treesit-simple-indent-rules js--treesit-indent-rules)
     ;; Navigation.
-    (setq-local treesit-defun-type-regexp
-                (rx (or "class_declaration"
-                        "method_definition"
-                        "function_declaration"
-                        "lexical_declaration")))
+    (setq-local treesit-defun-type-regexp js--treesit-defun-type-regexp)
+
     (setq-local treesit-defun-name-function #'js--treesit-defun-name)
 
-    (setq-local treesit-thing-settings
-                `((javascript
-                   (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
-                   (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
-                   (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
-                   (text ,(js--regexp-opt-symbol '("comment"
-                                                   "string_fragment"))))))
+    (setq-local treesit-thing-settings js--treesit-thing-settings)
 
     ;; Fontification.
     (setq-local treesit-font-lock-settings js--treesit-font-lock-settings)
-    (setq-local treesit-font-lock-feature-list
-                '(( comment document definition)
-                  ( keyword string)
-                  ( assignment constant escape-sequence jsx number
-                    pattern string-interpolation)
-                  ( bracket delimiter function operator property)))
+    (setq-local treesit-font-lock-feature-list js--treesit-font-lock-feature-list)
 
     (when (treesit-ready-p 'jsdoc t)
       (setq-local treesit-range-settings
@@ -3983,17 +4007,11 @@ js-ts-mode
                    :local t
                    `(((comment) @capture (:match ,js--treesit-jsdoc-beginning-regexp @capture)))))
 
-      (setq c-ts-common--comment-regexp (rx (or "comment" "line_comment" "block_comment" "description"))))
+      (setq c-ts-common--comment-regexp js--treesit-jsdoc-comment-regexp))
 
     ;; Imenu
-    (setq-local treesit-simple-imenu-settings
-                `(("Function" "\\`function_declaration\\'" nil nil)
-                  ("Variable" "\\`lexical_declaration\\'"
-                   js--treesit-valid-imenu-entry nil)
-                  ("Class" ,(rx bos (or "class_declaration"
-                                        "method_definition")
-                                eos)
-                   nil nil)))
+    (setq-local treesit-simple-imenu-settings js--treesit-simple-imenu-settings)
+
     (treesit-major-mode-setup)
 
     (add-to-list 'auto-mode-alist
diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index 53340195386..35c61e4f66d 100644
--- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -893,13 +893,7 @@ css-mode-syntax-table
     (modify-syntax-entry ?? "." st)
     st))
 
-(defvar-keymap css-mode-map
-  :doc "Keymap used in `css-mode'."
-  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
-  ;; `info-complete-symbol' is not used.
-  "<remap> <complete-symbol>" #'completion-at-point
-  "C-c C-f" #'css-cycle-color-format
-  :menu
+(defvar css-mode--menu
   '("CSS"
     :help "CSS-specific features"
     ["Reformat block" fill-paragraph
@@ -910,7 +904,17 @@ css-mode-map
     ["Describe symbol" css-lookup-symbol
      :help "Display documentation for a CSS symbol"]
     ["Complete symbol" completion-at-point
-     :help "Complete symbol before point"]))
+     :help "Complete symbol before point"])
+    "Menu bar for `css-mode'")
+
+(defvar-keymap css-mode-map
+  :doc "Keymap used in `css-mode'."
+  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
+  ;; `info-complete-symbol' is not used.
+  "<remap> <complete-symbol>" #'completion-at-point
+  "C-c C-f" #'css-cycle-color-format
+  :menu
+  css-mode--menu)
 
 (eval-and-compile
   (defconst css--uri-re
@@ -1771,6 +1775,21 @@ css--extract-index-name
               (replace-regexp-in-string "[\n ]+" " " s)))
            res)))))))
 
+(defvar css--treesit-font-lock-feature-list
+  '((selector comment query keyword)
+    (property constant string)
+    (error variable function operator bracket))
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar css--treesit-simple-imenu-settings
+  `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
+      nil nil))
+  "Settings for `treesit-simple-imenu'.")
+
+(defvar css--treesit-defun-type-regexp
+  "rule_set"
+  "Settings for `treesit-defun-type-regexp'.")
+
 (define-derived-mode css-base-mode prog-mode "CSS"
   "Generic mode to edit Cascading Style Sheets (CSS).
 
@@ -1825,16 +1844,12 @@ css-ts-mode
     ;; Tree-sitter specific setup.
     (setq treesit-primary-parser (treesit-parser-create 'css))
     (setq-local treesit-simple-indent-rules css--treesit-indent-rules)
-    (setq-local treesit-defun-type-regexp "rule_set")
+    (setq-local treesit-defun-type-regexp css--treesit-defun-type-regexp)
     (setq-local treesit-defun-name-function #'css--treesit-defun-name)
     (setq-local treesit-font-lock-settings css--treesit-settings)
-    (setq-local treesit-font-lock-feature-list
-                '((selector comment query keyword)
-                  (property constant string)
-                  (error variable function operator bracket)))
-    (setq-local treesit-simple-imenu-settings
-                `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
-                    nil nil)))
+    (setq-local treesit-font-lock-feature-list css--treesit-font-lock-feature-list)
+    (setq-local treesit-simple-imenu-settings css--treesit-simple-imenu-settings)
+
     (treesit-major-mode-setup)
 
     (add-to-list 'auto-mode-alist '("\\.css\\'" . css-ts-mode))))
diff --git a/lisp/textmodes/html-ts-mode.el b/lisp/textmodes/html-ts-mode.el
index dad49b7ed4c..5700c2b406b 100644
--- a/lisp/textmodes/html-ts-mode.el
+++ b/lisp/textmodes/html-ts-mode.el
@@ -87,6 +87,31 @@ html-ts-mode--font-lock-settings
    `((attribute_name) @font-lock-variable-name-face))
   "Tree-sitter font-lock settings for `html-ts-mode'.")
 
+(defvar html-ts-mode--treesit-things-settings
+  `((html
+     (sexp ,(regexp-opt '("element"
+                          "text"
+                          "attribute"
+                          "value")))
+     (list ,(regexp-opt '("element")) 'symbols)
+     (sentence "tag")
+     (text ,(regexp-opt '("comment" "text")))))
+  "Settings for `treesit-thing-settings'.")
+
+(defvar html-ts-mode--treesit-font-lock-feature-list
+  '((comment keyword definition)
+    (property string)
+    () ())
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar html-ts-mode--treesit-simple-imenu-settings
+  '(("Element" "\\`tag_name\\'" nil nil))
+  "Settings for `treesit-simple-imenu'.")
+
+(defvar html-ts-mode--treesit-defun-type-regexp
+  "element"
+  "Settings for `treesit-defun-type-regexp'.")
+
 (defun html-ts-mode--defun-name (node)
   "Return the defun name of NODE.
 Return nil if there is no name or if NODE is not a defun node."
@@ -107,30 +132,18 @@ html-ts-mode
   (setq-local treesit-simple-indent-rules html-ts-mode--indent-rules)
 
   ;; Navigation.
-  (setq-local treesit-defun-type-regexp "element")
+  (setq-local treesit-defun-type-regexp html-ts-mode--treesit-defun-type-regexp)
 
   (setq-local treesit-defun-name-function #'html-ts-mode--defun-name)
 
-  (setq-local treesit-thing-settings
-              `((html
-                 (sexp ,(regexp-opt '("element"
-                                      "text"
-                                      "attribute"
-                                      "value")))
-                 (list ,(regexp-opt '("element")) 'symbols)
-                 (sentence "tag")
-                 (text ,(regexp-opt '("comment" "text"))))))
+  (setq-local treesit-thing-settings html-ts-mode--treesit-things-settings)
 
   ;; Font-lock.
   (setq-local treesit-font-lock-settings html-ts-mode--font-lock-settings)
-  (setq-local treesit-font-lock-feature-list
-              '((comment keyword definition)
-                (property string)
-                () ()))
+  (setq-local treesit-font-lock-feature-list html-ts-mode--treesit-font-lock-feature-list)
 
   ;; Imenu.
-  (setq-local treesit-simple-imenu-settings
-              '(("Element" "\\`tag_name\\'" nil nil)))
+  (setq-local treesit-simple-imenu-settings html-ts-mode--treesit-simple-imenu-settings)
 
   ;; Outline minor mode.
   (setq-local treesit-outline-predicate "\\`element\\'")
diff --git a/lisp/treesit.el b/lisp/treesit.el
index 9e3c75519f9..ad3b8843407 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -1317,6 +1317,40 @@ treesit-font-lock-recompute-features
                        ((memq feature remove-list) nil)
                        (t current-value))))))
 
+(defun treesit-merge-font-lock-feature-list (features-list-1 features-list-2)
+  "Merge two tree-sitter font lock feature lists.
+Returns a new font lock feature list with no duplicates in the same level.
+It can be used to merge font lock feature lists in a multi-language major mode.
+FEATURES-LIST-1 and FEATURES-LIST-2 are list of lists of feature symbols."
+    (let ((result nil)
+	(features-1 (car features-list-1))
+	(features-2 (car features-list-2)))
+    (while (or features-1 features-2)
+      (cond
+       ((and features-1 (not features-2)) (push features-1 result))
+       ((and (not features-1) features-2) (push features-2 result))
+       ((and features-1 features-2) (push (cl-union features-1 features-2) result)))
+      (setq features-list-1 (cdr features-list-1)
+	    features-list-2 (cdr features-list-2)
+	    features-1 (car features-list-1)
+            features-2 (car features-list-2)))
+    (nreverse result)))
+
+(defun treesit-replace-font-lock-feature-settings (new-settings settings)
+  "Replaces :feature in SETTINGS with :feature from NEW-SETTINGS.
+Both SETTINGS and NEW-SETTINGS must be a value suitable for
+`treesit-font-lock-settings'.
+Return a value suitable for `treesit-font-lock-settings'"
+  (let ((result nil))
+    (dolist (new-setting new-settings)
+      (let ((new-feature (treesit-font-lock-setting-feature new-setting)))
+	(dolist (setting settings)
+	  (let ((feature (treesit-font-lock-setting-feature setting)))
+	    (if (eq new-feature feature)
+		(push new-setting result)
+	      (push setting result))))))
+    (nreverse result)))
+
 (defun treesit-add-font-lock-rules (rules &optional how feature)
   "Add font-lock RULES to the current buffer.
 
@@ -2464,6 +2498,71 @@ treesit-add-simple-indent-rules
               (append rules existing-rules)))))
     (setf (alist-get language treesit-simple-indent-rules) new-rules)))
 
+(defun treesit-modify-indent-rules (lang new-rules rules &optional how)
+  "Modify a copy of RULES using NEW-RULES.
+As default replace rules with the same anchor.
+When HOW is :prepend NEW-RULES are prepend to RULES, when
+:append NEW-RULES are appended to RULES, when :replace (the default)
+NEW-RULES replace rule in RULES which the same anchor."
+  (cond
+   ((not (alist-get lang rules))
+    (error "No rules for language %s in RULES" lang))
+   ((not (alist-get lang new-rules))
+    (error "No rules for language %s in NEW-RULES" lang))
+   (t (let* ((copy-of-rules (copy-tree js--treesit-indent-rules))
+	     (lang-rules (alist-get lang copy-of-rules))
+	     (lang-new-rules (alist-get lang new-rules)))
+	(cond
+	 ((eq how :prepend)
+	  (setf (alist-get lang copy-of-rules)
+		(append lang-new-rules lang-rules)))
+	 ((eq how :append)
+	  (setf (alist-get lang copy-of-rules)
+		(append lang-rules lang-new-rules)))
+	 ((or (eq how :replace) t)
+	  (let ((tail-new-rules lang-new-rules)
+		(tail-rules lang-rules)
+		(new-rule nil)
+		(rule nil))
+	    (while (setq new-rule (car tail-new-rules))
+	      (while (setq rule (car tail-rules))
+		(when (equal (nth 0 new-rule) (nth 0 rule))
+		  (setf (car tail-rules) new-rule))
+		(setq tail-rules (cdr tail-rules)))
+	      (setq tail-new-rules (cdr tail-new-rules))))))
+	copy-of-rules))))
+
+;; (defun treesit-modify-indent-rules (new-rules rules &optional how)
+;;   "Modify RULES using NEW-RULES.
+;; As default replace rules with the same anchor.
+;; When HOW is :prepend NEW-RULES are prepend to RULES, when
+;; :append NEW-RULES are appended to RULES, when :replace (the default)
+;; NEW-RULES replace rule in RULES which the same anchor."
+;;   (let ((n-lang (car (car new-rules)))
+;; 	(lang (car (car rules))))
+;;     (when (not (eq n-lang lang))
+;;       (error "The language must be the same"))
+;;     (let* ((nr (cdr (car new-rules)))
+;;            (r (cdr (car rules)))
+;;            (tmp nil)
+;;            (result
+;;             (cond
+;;              ((eq how :prepend)
+;; 	      (append nr r))
+;;              ((eq how :append)
+;;               (append r nr))
+;;              ((or (eq how :replace) t)
+;;               (nreverse
+;;                (progn
+;;                  (dolist (new-rule nr)
+;; 	           (dolist (rule r)
+;; 	             (if (equal (nth 0 new-rule) (nth 0 rule))
+;; 		         (push new-rule tmp)
+;; 		       (push rule tmp))))
+;;                  tmp))))))
+;;       (push lang result)
+;;       `(,result))))
+
 ;;; Search
 
 (defun treesit-search-forward-goto
-- 
2.48.1


--nextPart26760901.1r3eYUQgxm--







Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 8 Feb 2025 09:02:09 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat Feb 08 04:02:09 2025
Received: from localhost ([127.0.0.1]:38413 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tggj0-0004MA-Gd
	for submit <at> debbugs.gnu.org; Sat, 08 Feb 2025 04:02:08 -0500
Received: from eggs.gnu.org ([2001:470:142:3::10]:55366)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <eliz@HIDDEN>) id 1tggiv-0004La-HP
 for 74610 <at> debbugs.gnu.org; Sat, 08 Feb 2025 04:02:04 -0500
Received: from fencepost.gnu.org ([2001:470:142:3::e])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <eliz@HIDDEN>)
 id 1tggip-0005yn-O8; Sat, 08 Feb 2025 04:01:55 -0500
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org;
 s=fencepost-gnu-org; h=MIME-version:References:Subject:In-Reply-To:To:From:
 Date; bh=Kbw4YOKDQpESWBsVR3wBhZF/nGw+o+OZ+8D299Te13E=; b=ZTf7Aahd/guBQNCtTN3T
 iFdBqqILQN6J55RWuT+8lpmSm9zAtELBbRJRTRAzC5n9SH5xmLWlww5HCUx+67yTrQCXhxxECl8kf
 Hg1HyE6NjC7humFfQkhHMtq9Nov4myRQBAv5LiQTYTdzfSuUdZYJ4Hz2VZWoFgdtaWwz09y0NepT9
 d/pdPIACSnzC8ZRh5WrGoauxyJGwAOZKC3jFTX2qxxfer+GdHYPOPe5N1+EXsqEp9UfMdTz47oVi/
 iILL9PXgNWJ5eZyu87xBCbmsdRR1GL58bAtGV2ouPqQt4NNjmnLjntTdobv+E+SSyL6VB3S32qCwS
 JCXkmQLiowslww==;
Date: Sat, 08 Feb 2025 11:01:48 +0200
Message-Id: <864j144xoj.fsf@HIDDEN>
From: Eli Zaretskii <eliz@HIDDEN>
To: casouri@HIDDEN, juri@HIDDEN, Vincenzo Pupillo <v.pupillo@HIDDEN>
In-Reply-To: <26756353.1r3eYUQgxm@fedora> (message from Vincenzo Pupillo on
 Sun, 19 Jan 2025 18:09:31 +0100)
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
References: <3532547.LZWGnKmheA@fedora> <2462456.cojqenx9y0@fedora>
 <2ABDEC5D-B17E-4DD3-9371-48B61026A144@HIDDEN> <26756353.1r3eYUQgxm@fedora>
MIME-version: 1.0
Content-type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -2.3 (--)
X-Debbugs-Envelope-To: 74610
Cc: 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -3.3 (---)

Ping! Can we please make progress with this issue?  is the patch ready
to be installed?

> From: Vincenzo Pupillo <v.pupillo@HIDDEN>
> Cc: Juri Linkov <juri@HIDDEN>, 74610 <at> debbugs.gnu.org,
>  Eli Zaretskii <eliz@HIDDEN>
> Date: Sun, 19 Jan 2025 18:09:31 +0100
> 
> Ciao Yuan,
> In data sabato 18 gennaio 2025 02:37:53 Ora standard dell’Europa centrale, 
> Yuan Fu ha scritto:
> > > On Jan 14, 2025, at 1:41 PM, Vincenzo Pupillo <v.pupillo@HIDDEN> wrote:
> > > 
> > > Ciao Yuan and Juri,
> > > this is an updated version of mhtml-ts-mode.
> > > I have tried to reduce as much as possible copies of parts of the major
> > > modes from which it is derived.
> > > To do this, I had to move some values that were assigned directly to
> > > treesit's own variables  (in ccs-mode.el, in js.el, and in
> > > html-ts-mode.el) into new variables.
> > > I also added three new functions to treesit.el to make it easier to
> > > combine
> > > parts derived from the other major modes. So now any changes to these new
> > > variables are directly reflected in the behavior of mhtml-ts-mode.
> > > There are a few things I would like to highlight:
> > > 1. treesit-font-lock-feature-lists are not defined per parser, so simply
> > > merging the different lists will cause display differences from the
> > > original major-modes; for example “function” is defined at level 3 in
> > > css-ts-mode but at level 4 in js-ts-mode.
> > 
> > Yeah, that’s not pretty, I don’t have a good solution for now. Technically a
> > major mode can use treesit-font-lock-recompute-features after
> > treesit-major-mode-setup to do fine adjustments, but it’s ugly.
> > > 2. treesit-defun-type-regexp has the same problem as treesit-font-lock-
> > > feature-list, so I had to define it myself.
> > 
> > Maybe you can use treesit-thing-settings instead?
> Ok, done.
> 
> > 
> > > But other than that, it works pretty well.
> > > 
> > > IMHO, a global "list" where you can define "font-lock", "indent-list",
> > > "font- lock-feature", etc. by language (perhaps with getter and setter
> > > methods) might make it easier to define new multilingual major-modes. It
> > > could improve the decoupling between multilingual major-modes and the
> > > major-modes they are derived from. It could also better decouple the
> > > internal implementation of treesit.el from the treesitter-based
> > > major-modes.
> > > 
> > > Let me know what you think.
> > 
> > That’s what I had in mind. That’s also why I added a new variable
> > treesit-aggregated-simple-imenu-settings instead of piggybacking on
> > treesit-simple-imenu-settings. A separate variable is simpler to borrow
> > settings from.
> Yes, in this new patch I used treesit-aggregated-simple-imenu-settings.
> 
> > But I didn’t have time to think it though yet. (By language, or by mode and
> > language? Should packages set it on load, or define a function that loads
> > these settings? Etc.)
> >
> I would say by mode and language, both mhtml-ts mode and html-ts mode have the 
> same primary parser. For the second question, I don't know. I don't have 
> enough experience with it to know what the pros and cons are.
> 
> > Yuan
> 
> Vincenzo
> 
> From 199af27f1d761d100f6a8a6163c27e35e6ab2ea0 Mon Sep 17 00:00:00 2001
> From: Vincenzo Pupillo <v.pupillo@HIDDEN>
> Date: Sun, 19 Jan 2025 17:49:30 +0100
> Subject: [PATCH] Add mhtml-ts-mode.
> 
> New major-mode alternative to mhtml-mode, based on treesitter, for
> editing files containing html, javascript and css.
> 
> * etc/NEWS: Mention the new mode and new functions.
> * lisp/textmodes/mhtml-ts-mode.el: New file.
> * lisp/progmodes/js.el (js--treesit-thing-settings): New variable.
> (js--treesit-font-lock-feature-list); New variable.
> (js--treesit-simple-imenu-settings): New variable.
> (js--treesit-defun-type-regexp): New variable.
> (js--treesit-jsdoc-comment-regexp): New variable.
> (js-ts-mode): Use of new variables instead of direct assignment of
> values.
> * lisp/textmodes/css-mode.el (css-mode--menu): New variable.
> (css-mode-map): Use new variable.
> (css--treesit-font-lock-feature-list): New variable.
> (css--treesit-simple-imenu-settings): New variable.
> (css--treesit-defun-type-regexp): New variable.
> (cs-ts-mode): Use of new variables instead of direct assignment of
> values.
> * lisp/textmodes/html-ts-mode.el
> (html-ts-mode--treesit-things-settings): New variable.
> (html-ts-mode--treesit-font-lock-feature-list): New variable.
> (html-ts-mode--treesit-simple-imenu-settings): New variable.
> (html-ts-mode--treesit-defun-type-regexp): New variable.
> (html-ts-mode): Use of new variables instead of direct assignment of
> values.
> * lisp/treesit.el
> (treesit-merge-font-lock-feature-list): New fuction.
> (treesit-replace-font-lock-feature-settings): New fuction.
> (treesit-modify-indent-rules): New function.
> ---
>  etc/NEWS                        |  28 ++
>  lisp/progmodes/js.el            |  72 ++--
>  lisp/textmodes/css-mode.el      |  47 ++-
>  lisp/textmodes/html-ts-mode.el  |  45 ++-
>  lisp/textmodes/mhtml-ts-mode.el | 600 ++++++++++++++++++++++++++++++++
>  lisp/treesit.el                 |  65 ++++
>  6 files changed, 798 insertions(+), 59 deletions(-)
>  create mode 100644 lisp/textmodes/mhtml-ts-mode.el
> 
> diff --git a/etc/NEWS b/etc/NEWS
> index 0b849dec450..6360a082e9f 100644
> --- a/etc/NEWS
> +++ b/etc/NEWS
> @@ -965,6 +965,12 @@ destination window is chosen using 'display-buffer-alist'.  Example:
>  
>  
>  * New Modes and Packages in Emacs 31.1
> +** New major modes based on the tree-sitter library
> +
> +*** New major mode 'mhtml-ts-mode'.
> +An optional major mode based on the tree-sitter library for editing html
> +files. This mode handles indentation, fontification, and commenting for
> +embedded JavaScript and CSS.
>  
>  
>  * Incompatible Lisp Changes in Emacs 31.1
> @@ -1098,6 +1104,28 @@ language symbol.  For example, 'cpp' is translated to "C++".  A new
>  variable 'treesit-language-display-name-alist' holds the translations of
>  language symbols where that translation is not trivial.
>  
> ++++
> +*** New function 'treesit-merge-font-lock-feature-list'.
> +This function the merge two tree-sitter font lock feature lists.
> +Returns a new font lock feature list with no duplicates in the same level.
> +It can be used to merge font lock feature lists in a multi-language major mode.
> +
> ++++
> +*** New function 'treesit-replace-font-lock-feature-settings'.
> +Given two treesit-font-lock-settings replaces the feature in the second
> +font-lock-settings with the same feature in the first
> +font-lock-settings. In a multi-linguage major mode it is sometimes
> +necessary to replace features from one of the major modes, with others
> +that are better suited to the new multilingual context.
> +
> ++++
> +*** New function 'treesit-modify-indent-rules'.
> +Given two treesit ident rules, it replaces, adds, or prepends the new
> +rules to the old ones, then returns a new treesit indent rules.
> +In a multi-linguage major mode it is sometimes necessary to modify rules
> +from one of the major modes, with others that are better suited to the
> +new multilingual context.
> +
>  +++
>  *** New command 'treesit-explore'
>  This command replaces 'treesit-explore-mode'.  It turns on
> diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
> index 101b882c718..be9d6f64aef 100644
> --- a/lisp/progmodes/js.el
> +++ b/lisp/progmodes/js.el
> @@ -3901,6 +3901,44 @@ js--treesit-list-nodes
>  (defvar js--treesit-jsdoc-beginning-regexp (rx bos "/**")
>    "Regular expression matching the beginning of a jsdoc block comment.")
>  
> +(defvar js--treesit-thing-settings
> +  `((javascript
> +     (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
> +     (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
> +     (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
> +     (text ,(js--regexp-opt-symbol '("comment"
> +                                     "string_fragment")))))
> +  "Settings for `treesit-thing-settings'.")
> +
> +(defvar js--treesit-font-lock-feature-list
> +  '(( comment document definition)
> +    ( keyword string)
> +    ( assignment constant escape-sequence jsx number
> +      pattern string-interpolation)
> +    ( bracket delimiter function operator property))
> +  "Settings for `treesit-font-lock-feature-list'.")
> +
> +(defvar js--treesit-simple-imenu-settings
> +  `(("Function" "\\`function_declaration\\'" nil nil)
> +    ("Variable" "\\`lexical_declaration\\'"
> +     js--treesit-valid-imenu-entry nil)
> +    ("Class" ,(rx bos (or "class_declaration"
> +                          "method_definition")
> +                  eos)
> +     nil nil))
> +  "Settings for `treesit-simple-imenu'.")
> +
> +(defvar js--treesit-defun-type-regexp
> +  (rx (or "class_declaration"
> +          "method_definition"
> +          "function_declaration"
> +          "lexical_declaration"))
> +  "Settings for `treesit-defun-type-regexp'.")
> +
> +(defvar js--treesit-jsdoc-comment-regexp
> +  (rx (or "comment" "line_comment" "block_comment" "description"))
> +  "Regexp for `c-ts-common--comment-regexp'.")
> +
>  ;;;###autoload
>  (define-derived-mode js-ts-mode js-base-mode "JavaScript"
>    "Major mode for editing JavaScript.
> @@ -3931,29 +3969,15 @@ js-ts-mode
>      ;; Indent.
>      (setq-local treesit-simple-indent-rules js--treesit-indent-rules)
>      ;; Navigation.
> -    (setq-local treesit-defun-type-regexp
> -                (rx (or "class_declaration"
> -                        "method_definition"
> -                        "function_declaration"
> -                        "lexical_declaration")))
> +    (setq-local treesit-defun-type-regexp js--treesit-defun-type-regexp)
> +
>      (setq-local treesit-defun-name-function #'js--treesit-defun-name)
>  
> -    (setq-local treesit-thing-settings
> -                `((javascript
> -                   (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
> -                   (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
> -                   (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
> -                   (text ,(js--regexp-opt-symbol '("comment"
> -                                                   "string_fragment"))))))
> +    (setq-local treesit-thing-settings js--treesit-thing-settings)
>  
>      ;; Fontification.
>      (setq-local treesit-font-lock-settings js--treesit-font-lock-settings)
> -    (setq-local treesit-font-lock-feature-list
> -                '(( comment document definition)
> -                  ( keyword string)
> -                  ( assignment constant escape-sequence jsx number
> -                    pattern string-interpolation)
> -                  ( bracket delimiter function operator property)))
> +    (setq-local treesit-font-lock-feature-list js--treesit-font-lock-feature-list)
>  
>      (when (treesit-ready-p 'jsdoc t)
>        (setq-local treesit-range-settings
> @@ -3963,17 +3987,11 @@ js-ts-mode
>                     :local t
>                     `(((comment) @capture (:match ,js--treesit-jsdoc-beginning-regexp @capture)))))
>  
> -      (setq c-ts-common--comment-regexp (rx (or "comment" "line_comment" "block_comment" "description"))))
> +      (setq c-ts-common--comment-regexp js--treesit-jsdoc-comment-regexp))
>  
>      ;; Imenu
> -    (setq-local treesit-simple-imenu-settings
> -                `(("Function" "\\`function_declaration\\'" nil nil)
> -                  ("Variable" "\\`lexical_declaration\\'"
> -                   js--treesit-valid-imenu-entry nil)
> -                  ("Class" ,(rx bos (or "class_declaration"
> -                                        "method_definition")
> -                                eos)
> -                   nil nil)))
> +    (setq-local treesit-simple-imenu-settings js--treesit-simple-imenu-settings)
> +
>      (treesit-major-mode-setup)
>  
>      (add-to-list 'auto-mode-alist
> diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
> index 53340195386..35c61e4f66d 100644
> --- a/lisp/textmodes/css-mode.el
> +++ b/lisp/textmodes/css-mode.el
> @@ -893,13 +893,7 @@ css-mode-syntax-table
>      (modify-syntax-entry ?? "." st)
>      st))
>  
> -(defvar-keymap css-mode-map
> -  :doc "Keymap used in `css-mode'."
> -  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
> -  ;; `info-complete-symbol' is not used.
> -  "<remap> <complete-symbol>" #'completion-at-point
> -  "C-c C-f" #'css-cycle-color-format
> -  :menu
> +(defvar css-mode--menu
>    '("CSS"
>      :help "CSS-specific features"
>      ["Reformat block" fill-paragraph
> @@ -910,7 +904,17 @@ css-mode-map
>      ["Describe symbol" css-lookup-symbol
>       :help "Display documentation for a CSS symbol"]
>      ["Complete symbol" completion-at-point
> -     :help "Complete symbol before point"]))
> +     :help "Complete symbol before point"])
> +    "Menu bar for `css-mode'")
> +
> +(defvar-keymap css-mode-map
> +  :doc "Keymap used in `css-mode'."
> +  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
> +  ;; `info-complete-symbol' is not used.
> +  "<remap> <complete-symbol>" #'completion-at-point
> +  "C-c C-f" #'css-cycle-color-format
> +  :menu
> +  css-mode--menu)
>  
>  (eval-and-compile
>    (defconst css--uri-re
> @@ -1771,6 +1775,21 @@ css--extract-index-name
>                (replace-regexp-in-string "[\n ]+" " " s)))
>             res)))))))
>  
> +(defvar css--treesit-font-lock-feature-list
> +  '((selector comment query keyword)
> +    (property constant string)
> +    (error variable function operator bracket))
> +  "Settings for `treesit-font-lock-feature-list'.")
> +
> +(defvar css--treesit-simple-imenu-settings
> +  `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
> +      nil nil))
> +  "Settings for `treesit-simple-imenu'.")
> +
> +(defvar css--treesit-defun-type-regexp
> +  "rule_set"
> +  "Settings for `treesit-defun-type-regexp'.")
> +
>  (define-derived-mode css-base-mode prog-mode "CSS"
>    "Generic mode to edit Cascading Style Sheets (CSS).
>  
> @@ -1825,16 +1844,12 @@ css-ts-mode
>      ;; Tree-sitter specific setup.
>      (setq treesit-primary-parser (treesit-parser-create 'css))
>      (setq-local treesit-simple-indent-rules css--treesit-indent-rules)
> -    (setq-local treesit-defun-type-regexp "rule_set")
> +    (setq-local treesit-defun-type-regexp css--treesit-defun-type-regexp)
>      (setq-local treesit-defun-name-function #'css--treesit-defun-name)
>      (setq-local treesit-font-lock-settings css--treesit-settings)
> -    (setq-local treesit-font-lock-feature-list
> -                '((selector comment query keyword)
> -                  (property constant string)
> -                  (error variable function operator bracket)))
> -    (setq-local treesit-simple-imenu-settings
> -                `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
> -                    nil nil)))
> +    (setq-local treesit-font-lock-feature-list css--treesit-font-lock-feature-list)
> +    (setq-local treesit-simple-imenu-settings css--treesit-simple-imenu-settings)
> +
>      (treesit-major-mode-setup)
>  
>      (add-to-list 'auto-mode-alist '("\\.css\\'" . css-ts-mode))))
> diff --git a/lisp/textmodes/html-ts-mode.el b/lisp/textmodes/html-ts-mode.el
> index dad49b7ed4c..5700c2b406b 100644
> --- a/lisp/textmodes/html-ts-mode.el
> +++ b/lisp/textmodes/html-ts-mode.el
> @@ -87,6 +87,31 @@ html-ts-mode--font-lock-settings
>     `((attribute_name) @font-lock-variable-name-face))
>    "Tree-sitter font-lock settings for `html-ts-mode'.")
>  
> +(defvar html-ts-mode--treesit-things-settings
> +  `((html
> +     (sexp ,(regexp-opt '("element"
> +                          "text"
> +                          "attribute"
> +                          "value")))
> +     (list ,(regexp-opt '("element")) 'symbols)
> +     (sentence "tag")
> +     (text ,(regexp-opt '("comment" "text")))))
> +  "Settings for `treesit-thing-settings'.")
> +
> +(defvar html-ts-mode--treesit-font-lock-feature-list
> +  '((comment keyword definition)
> +    (property string)
> +    () ())
> +  "Settings for `treesit-font-lock-feature-list'.")
> +
> +(defvar html-ts-mode--treesit-simple-imenu-settings
> +  '(("Element" "\\`tag_name\\'" nil nil))
> +  "Settings for `treesit-simple-imenu'.")
> +
> +(defvar html-ts-mode--treesit-defun-type-regexp
> +  "element"
> +  "Settings for `treesit-defun-type-regexp'.")
> +
>  (defun html-ts-mode--defun-name (node)
>    "Return the defun name of NODE.
>  Return nil if there is no name or if NODE is not a defun node."
> @@ -107,30 +132,18 @@ html-ts-mode
>    (setq-local treesit-simple-indent-rules html-ts-mode--indent-rules)
>  
>    ;; Navigation.
> -  (setq-local treesit-defun-type-regexp "element")
> +  (setq-local treesit-defun-type-regexp html-ts-mode--treesit-defun-type-regexp)
>  
>    (setq-local treesit-defun-name-function #'html-ts-mode--defun-name)
>  
> -  (setq-local treesit-thing-settings
> -              `((html
> -                 (sexp ,(regexp-opt '("element"
> -                                      "text"
> -                                      "attribute"
> -                                      "value")))
> -                 (list ,(regexp-opt '("element")) 'symbols)
> -                 (sentence "tag")
> -                 (text ,(regexp-opt '("comment" "text"))))))
> +  (setq-local treesit-thing-settings html-ts-mode--treesit-things-settings)
>  
>    ;; Font-lock.
>    (setq-local treesit-font-lock-settings html-ts-mode--font-lock-settings)
> -  (setq-local treesit-font-lock-feature-list
> -              '((comment keyword definition)
> -                (property string)
> -                () ()))
> +  (setq-local treesit-font-lock-feature-list html-ts-mode--treesit-font-lock-feature-list)
>  
>    ;; Imenu.
> -  (setq-local treesit-simple-imenu-settings
> -              '(("Element" "\\`tag_name\\'" nil nil)))
> +  (setq-local treesit-simple-imenu-settings html-ts-mode--treesit-simple-imenu-settings)
>  
>    ;; Outline minor mode.
>    (setq-local treesit-outline-predicate "\\`element\\'")
> diff --git a/lisp/textmodes/mhtml-ts-mode.el b/lisp/textmodes/mhtml-ts-mode.el
> new file mode 100644
> index 00000000000..272d00a1ef6
> --- /dev/null
> +++ b/lisp/textmodes/mhtml-ts-mode.el
> @@ -0,0 +1,600 @@
> +;;; mhtml-ts-mode.el --- Major mode for HTML using tree-sitter -*- lexical-binding: t; -*-
> +
> +;; Copyright (C) 2024 Free Software Foundation, Inc.
> +
> +;; Author: Vincenzo Pupillo <v.pupillo@HIDDEN>
> +;; Maintainer: Vincenzo Pupillo <v.pupillo@HIDDEN>
> +;; Created: Nov 2024
> +;; Keywords: HTML languages hypermedia tree-sitter
> +
> +;; This file is part of GNU Emacs.
> +
> +;; GNU Emacs is free software: you can redistribute it and/or modify
> +;; it under the terms of the GNU General Public License as published by
> +;; the Free Software Foundation, either version 3 of the License, or
> +;; (at your option) any later version.
> +
> +;; GNU Emacs is distributed in the hope that it will be useful,
> +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
> +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +;; GNU General Public License for more details.
> +
> +;; You should have received a copy of the GNU General Public License
> +;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
> +
> +;;; Commentary:
> +;;
> +;; This package provides `mhtml-ts-mode' which is a major mode
> +;; for editing HTML files with embedded JavaScript and CSS.
> +;; Tree Sitter is used to parse each of these languages.
> +;;
> +;; Please note that this package requires `html-ts-mode', which
> +;; registers itself as the major mode for editing HTML.
> +;;
> +;; This package is compatible and has been tested with the following
> +;; tree-sitter grammars:
> +;; * https://github.com/tree-sitter/tree-sitter-html
> +;; * https://github.com/tree-sitter/tree-sitter-javascript
> +;; * https://github.com/tree-sitter/tree-sitter-jsdoc
> +;; * https://github.com/tree-sitter/tree-sitter-css
> +;;
> +;; Features
> +;;
> +;; * Indent
> +;; * Flymake
> +;; * IMenu
> +;; * Navigation
> +;; * Which-function
> +;; * Tree-sitter parser installation helper
> +
> +;;; Code:
> +
> +(require 'treesit)
> +(require 'html-ts-mode)
> +(require 'css-mode) ;; for embed css into html
> +(require 'js) ;; for embed javascript into html
> +
> +(eval-when-compile
> +  (require 'rx))
> +
> +;; This tells the byte-compiler where the functions are defined.
> +;; Is only needed when a file needs to be able to byte-compile
> +;; in a Emacs not built with tree-sitter library.
> +(treesit-declare-unavailable-functions)
> +
> +;; In a multi-language major mode can be useful to have an "installer" to
> +;; simplify the installation of the grammars supported by the major-mode.
> +(defvar mhtml-ts-mode--language-source-alist
> +  '((html . ("https://github.com/tree-sitter/tree-sitter-html"  "v0.23.2"))
> +    (javascript . ("https://github.com/tree-sitter/tree-sitter-javascript" "v0.23.1"))
> +    (jsdoc . ("https://github.com/tree-sitter/tree-sitter-jsdoc" "v0.23.2"))
> +    (css . ("https://github.com/tree-sitter/tree-sitter-css" "v0.23.1")))
> +  "Treesitter language parsers required by `mhtml-ts-mode'.
> +You can customize this variable if you want to stick to a specific
> +commit and/or use different parsers.")
> +
> +(defun mhtml-ts-mode-install-parsers ()
> +  "Install all the required treesitter parsers.
> +`mhtml-ts-mode--language-source-alist' defines which parsers to install."
> +  (interactive)
> +  (let ((treesit-language-source-alist mhtml-ts-mode--language-source-alist))
> +    (dolist (item mhtml-ts-mode--language-source-alist)
> +      (treesit-install-language-grammar (car item)))))
> +
> +;;; Custom variables
> +
> +(defgroup mhtml-ts-mode nil
> +  "Major mode for editing HTML files, based on `html-ts-mode'.
> +Works with JS and CSS and for that use `js-ts-mode' and `css-ts-mode'."
> +  :prefix "mhtml-ts-mode-"
> +  ;; :group 'languages
> +  :group 'html)
> +
> +(defcustom mhtml-ts-mode-js-css-indent-offset 2
> +  "JavaScript and CSS indent spaces related to the <script> and <style> HTML tags.
> +By default should have same value as `html-ts-mode-indent-offset'."
> +  :tag "HTML javascript or css indent offset"
> +  :version "31.1"
> +  :type 'integer
> +  :safe 'integerp)
> +
> +(defcustom mhtml-ts-mode-pretty-print-command
> +  ;; prefer tidy because it's used by sgml-mode
> +  (let ((executable nil))
> +    (cond ((setq executable (executable-find "tidy"))
> +           (format
> +            "%s --gnu-emacs yes --wrap 0 --indent-spaces %s -q -i -"
> +            executable html-ts-mode-indent-offset))
> +          ((setq executable (executable-find "xmllint"))
> +           (format "%s --html --quiet --format -" executable))
> +          (t "Install tidy, ore some other HTML pretty print tool, and set `mhtml-ts-mode-pretty-print-command'.")))
> +  "The command to pretty print the current HTML buffer."
> +  :type 'string
> +  :version "31.1")
> +
> +(defvar mhtml-ts-mode--js-css-indent-offset
> +  mhtml-ts-mode-js-css-indent-offset
> +  "Internal copy of `mhtml-ts-mode-js-css-indent-offset'.
> +The value changes, by `mhtml-ts-mode--tag-relative-indent-offset' according to
> +the value of `mhtml-ts-mode-tag-relative-indent'.")
> +
> +(defun mhtml-ts-mode--tag-relative-indent-offset (sym val)
> +  "Custom setter for `mhtml-ts-mode-tag-relative-indent'.
> +Apart from setting the default value of SYM to VAL, also change the
> +value of SYM in `mhtml-ts-mode' buffers to VAL.  SYM should be
> +`mhtml-ts-mode-tag-relative-indent', and VAL should be t, nil or
> +`ignore'.  When sym is `mhtml-ts-mode-tag-relative-indent' set the
> +value of `mhtml-ts-mode--js-css-indent-offset' to 0 if VAL is t,
> +otherwise to `mhtml-ts-mode-js-css-indent-offset'."
> +  (set-default sym val)
> +  (when (eq sym 'mhtml-ts-mode-tag-relative-indent)
> +    (setq
> +     mhtml-ts-mode--js-css-indent-offset
> +     (if (eq val t)
> +         mhtml-ts-mode-js-css-indent-offset
> +       0))))
> +
> +(defcustom mhtml-ts-mode-tag-relative-indent t
> +  "How <script> and <style> bodies are indented relative to the tag.
> +
> +When t, indentation looks like:
> +
> +  <script>
> +    code();
> +  </script>
> +
> +When nil, indentation of the tag body starts just below the
> +tag, like:
> +
> +  <script>
> +  code();
> +  </script>
> +
> +When `ignore', the tag body starts in the first column, like:
> +
> +  <script>
> +code();
> +  </script>"
> +  :type '(choice (const nil) (const t) (const ignore))
> +  :safe 'symbolp
> +  :set #'mhtml-ts-mode--tag-relative-indent-offset
> +  :version "31.1")
> +
> +(defcustom mhtml-ts-mode-css-fontify-colors t
> +  "Whether CSS colors should be fontified using the color as the background.
> +If non-nil, text representing a CSS color will be fontified
> +such that its background is the color itself.
> +Works like `css--fontify-region'."
> +  :tag "HTML colors the CSS properties values."
> +  :version "31.1"
> +  :type 'boolean
> +  :safe 'booleanp)
> +
> +(defvar mhtml-ts-mode-saved-pretty-print-command nil
> +  "The command last used to pretty print in this buffer.")
> +
> +(defun mhtml-ts-mode-pretty-print (command)
> +  "Prettify the current buffer.
> +Argument COMMAND The command to use."
> +  (interactive
> +   (list (read-string
> +          "Prettify command: "
> +          (or mhtml-ts-mode-saved-pretty-print-command
> +              (concat mhtml-ts-mode-pretty-print-command " ")))))
> +  (setq mhtml-ts-mode-saved-pretty-print-command command)
> +  (save-excursion
> +    (shell-command-on-region
> +     (point-min) (point-max)
> +     command (buffer-name) t
> +     "*mhtml-ts-mode-pretty-pretty-print-errors*" t)))
> +
> +(defun mhtml-ts-mode--switch-fill-defun (&rest arguments)
> +  "Switch between `fill-paragraph' and `prog-fill-reindent-defun'.
> +In an HTML region it calls `fill-paragraph' as does `html-ts-mode',
> +otherwise it calls `prog-fill-reindent-defun'.
> +Optional ARGUMENTS to to be passed to it."
> +  (interactive)
> +  (if (eq (treesit-language-at (point)) 'html)
> +      (funcall-interactively #'fill-paragraph arguments)
> +    (funcall-interactively #'prog-fill-reindent-defun arguments)))
> +
> +(defvar-keymap mhtml-ts-mode-map
> +  :doc "Keymap for `mhtml-ts-mode' buffers."
> +  :parent html-mode-map
> +  ;; `mhtml-ts-mode' derive from `html-ts-mode' so the keymap is the
> +  ;; same, we need to add some mapping from others languages.
> +  "C-c C-f" #'css-cycle-color-format
> +  "M-q" #'mhtml-ts-mode--switch-fill-defun)
> +
> +;; Place the CSS menu in the menu bar as well.
> +(easy-menu-define mhtml-ts-mode-menu mhtml-ts-mode-map
> +  "Menu bar for `mhtml-ts-mode'."
> +  css-mode--menu)
> +
> +;; Not used at the moment.
> +(defun mthml-ts-mode--js-language-at-point (point)
> +  "Return the language at POINT assuming the point is within a Javascript region."
> +  (let* ((node (treesit-node-at point 'javascript))
> +         (node-type (treesit-node-type node))
> +         (node-start (treesit-node-start node))
> +         (node-end (treesit-node-end node)))
> +    (if (not (treesit-ready-p 'jsdoc t))
> +        'javascript
> +      (if (equal node-type "comment")
> +          (save-excursion
> +            ;; (message "node start = %s , end = %s" node-start node-end)
> +            (goto-char node-start)
> +            (if (search-forward "/**" node-end t)
> +                'jsdoc
> +              'javascript))
> +        'javascript))))
> +
> +;; To enable some basic treesiter functionality, you should define
> +;; a function that recognizes which grammar is used at-point.
> +;; This function should be assigned to `treesit-language-at-point-function'
> +(defun mhtml-ts-mode--language-at-point (point)
> +  "Return the language at POINT assuming the point is within a HTML buffer."
> +  (let* ((node (treesit-node-at point 'html))
> +         (parent (treesit-node-parent node))
> +         (node-query (format "(%s (%s))"
> +                             (treesit-node-type parent)
> +                             (treesit-node-type node))))
> +    (cond
> +     ((equal "(script_element (raw_text))" node-query) 'javascript)
> +     ;; ((equal "(script_element (raw_text))" node-query) 'mthml-ts-mode--js-language-at-point)
> +     ((equal "(style_element (raw_text))" node-query) 'css)
> +     (t 'html))))
> +
> +;; Custom font-lock function that's used to apply color to css color
> +;; The signature of the function should be conforming to signature
> +;; QUERY-SPEC required by `treesit-font-lock-rules'.
> +(defun mhtml-ts-mode--colorize-css-value (node override start end &rest _)
> +  "Colorize CSS property value like `css--fontify-region'.
> +For NODE, OVERRIDE, START, and END, see `treesit-font-lock-rules'."
> +  (if (and mhtml-ts-mode-css-fontify-colors
> +           (string-equal "plain_value" (treesit-node-type node)))
> +      (let ((color (css--compute-color start (treesit-node-text node t))))
> +        (when color
> +          (with-silent-modifications
> +            (add-text-properties
> +             (treesit-node-start node) (treesit-node-end node)
> +             (list 'face (list :background color
> +                               :foreground (readable-foreground-color
> +                                            color)
> +                               :box '(:line-width -1)))))))
> +    (treesit-fontify-with-override
> +     (treesit-node-start node) (treesit-node-end node)
> +     'font-lock-variable-name-face
> +     override start end)))
> +
> +;; Embedded languages ​​should be indented according to the language
> +;; that embeds them.
> +;; This function signature complies with `treesit-simple-indent-rules'
> +;; ANCHOR.
> +(defun mhtml-ts-mode--js-css-tag-bol (_node _parent &rest _)
> +  "Find the first non-space characters of html tags <script> or <style>.
> +Return `line-beginning-position' when `treesit-node-at' is html, or
> +`mhtml-ts-mode-tag-relative-indent' is equal to ignore.
> +NODE and PARENT are ignored."
> +  (if (or (eq (treesit-language-at (point)) 'html)
> +          (eq mhtml-ts-mode-tag-relative-indent 'ignore))
> +      (line-beginning-position)
> +    ;; Ok, we are in js or css block.
> +    (save-excursion
> +      (re-search-backward "<script.*>\\|<style.*>" nil t))))
> +
> +;; Treesit supports 4 level of decoration, `treesit-font-lock-level'
> +;; define which level to use.  Major modes categorize their fontification
> +;; features, these categories are defined by `treesit-font-lock-rules' of
> +;; each major-mode using :feature keyword.
> +;; In a multiple language Major mode it's a good idea to provide, for each
> +;; level, the union of the :feature of the same level.
> +;; TODO: Since the feature-list is not defined per "parser" (like, for
> +;; example, the thing-settings), the same feature can appear in
> +;; different levels, so the appearance of a multiple main mode can be
> +;; different from the main mode used.  For e.g the feature "function" is
> +;; at level 4 for Javascript while it is at level 3 for CSS.
> +(defvar mhtml-ts-mode--treesit-font-lock-feature-list
> +  (treesit-merge-font-lock-feature-list
> +   html-ts-mode--treesit-font-lock-feature-list
> +   (treesit-merge-font-lock-feature-list
> +    js--treesit-font-lock-feature-list
> +    css--treesit-font-lock-feature-list))
> +  "Settings for `treesit-font-lock-feature-list'.")
> +
> +(defvar mhtml-ts-mode--treesit-font-lock-settings
> +  (append html-ts-mode--font-lock-settings
> +          js--treesit-font-lock-settings
> +          ;; Let's replace a css rule with a new one that adds color to
> +          ;; the css value.
> +          (treesit-replace-font-lock-feature-settings
> +           (treesit-font-lock-rules
> +            :language 'css
> +            :override t
> +            :feature 'variable
> +            '((plain_value) @font-lock-variable-name-face
> +              (plain_value) @mhtml-ts-mode--colorize-css-value))
> +           css--treesit-settings))
> +  "Settings for `treesit-font-lock-settings'.")
> +
> +(defvar mhtml-ts-mode--treesit-thing-settings
> +  ;; In addition to putting together the various definitions, we need to
> +  ;; add 'defun' which is used to support `imenu' and 'which-function'.
> +  (list
> +   ;; HTML thing settings
> +   (append
> +    (car html-ts-mode--treesit-things-settings)
> +    `((defun ,(regexp-opt (list html-ts-mode--treesit-defun-type-regexp)))))
> +   ;; Javascript thing settings
> +   (append
> +    (car js--treesit-thing-settings)
> +    `((defun ,js--treesit-defun-type-regexp)))
> +   ;; CSS thing settings
> +   `(css
> +     (defun ,(regexp-opt (list css--treesit-defun-type-regexp)))))
> +  "Settings for `treesit-thing-settings'.")
> +
> +(defvar mhtml-ts-mode--treesit-indent-rules
> +  (treesit--indent-rules-optimize
> +   (append html-ts-mode--indent-rules
> +           ;; Extended rules for js and css, to
> +           ;; indent appropriately when injected
> +           ;; into html
> +           (treesit-modify-indent-rules
> +            `((javascript ((parent-is "program")
> +                           mhtml-ts-mode--js-css-tag-bol
> +                           mhtml-ts-mode--js-css-indent-offset)))
> +            js--treesit-indent-rules
> +            :replace)
> +           (treesit-modify-indent-rules
> +            `((css ((parent-is "stylesheet")
> +                    mhtml-ts-mode--js-css-tag-bol
> +                    mhtml-ts-mode--js-css-indent-offset)))
> +            css--treesit-indent-rules 'prepend)
> +           :replace))
> +  "Settings for `treesit-simple-indent-rules'.")
> +
> +(defvar mhtml-ts-mode--treesit-aggregated-simple-imenu-settings
> +  `((html ,@html-ts-mode--treesit-simple-imenu-settings)
> +    (javascript ,@js--treesit-simple-imenu-settings)
> +    (css ,@css--treesit-simple-imenu-settings))
> +  "Settings for `treesit-simple-imenu'.")
> +
> +;; TODO: treesit-defun-type-regexp should have an aggregated version,
> +;; like treesit-aggregated-simple-imenu-settings. Otherwise we can't
> +;; reuse the regex defined in the major mode we use.
> +(defvar mhtml-ts-mode--treesit-defun-type-regexp
> +  (regexp-opt '("class_declaration"
> +                "method_definition"
> +                "function_declaration"
> +                "lexical_declaration"
> +                "element"
> +                "rule_set"))
> +  "Settings for `treesit-defun-type-regexp'.")
> +
> +;; In order to support `prettify-symbols-mode', just `append' the prettify
> +;; alist of all the languages. In our case only javascript defined this alist.
> +(defvar mhtml-ts-mode--prettify-symbols-alist js--prettify-symbols-alist
> +  "Alist of symbol prettifications for various supported languages.")
> +
> +;; In order to support `which-fuction-mode' we should define
> +;; a function that return the defun name.
> +;; In a multilingual treesit mode, this can be implemented simply by
> +;; calling language-specific functions.
> +(defun mhtml-ts-mode--defun-name (node)
> +  "Return the defun name of NODE.
> +Return nil if there is no name or if NODE is not a defun node."
> +    (let ((html-name (html-ts-mode--defun-name node))
> +          (js-name (js--treesit-defun-name node))
> +          (css-name (css--treesit-defun-name node)))
> +      (cond
> +       (html-name html-name)
> +       (js-name js-name)
> +       (css-name css-name))))
> +
> +;;; Flymake integration
> +
> +(defvar-local mhtml-ts-mode--flymake-process nil
> +  "Store the Flymake process.")
> +
> +(defun mhtml-ts-mode-flymake-mhtml (report-fn &rest _args)
> +  "MHTML backend for Flymake.
> +Calls REPORT-FN directly.  Requires tidy."
> +  (when (process-live-p mhtml-ts-mode--flymake-process)
> +    (kill-process mhtml-ts-mode--flymake-process))
> +  (let ((tidy (executable-find "tidy"))
> +        (source (current-buffer))
> +        (diagnostics-pattern (eval-when-compile
> +                               (rx bol
> +                                   "line " (group (+ num))    ;; :1 line
> +                                   " column " (group (+ num)) ;; :2 column
> +                                   " - " (group (+? nonl))    ;; :3 type
> +                                   ": " (group (+? nonl))     ;; :4 msg
> +                                   eol))))
> +    (if (not tidy)
> +        (error "Unable to find tidy command")
> +      (save-restriction
> +        (widen)
> +        (setq mhtml-ts-mode--flymake-process
> +              (make-process
> +               :name "mhtml-ts-mode-flymake"
> +               :noquery t
> +               :connection-type 'pipe
> +               :buffer (generate-new-buffer "*mhtml-ts-mode-flymake*")
> +               :command `(,tidy "--gnu-emacs" "yes" "-e" "-q")
> +               :sentinel
> +               (lambda (proc _event)
> +                 (when (eq 'exit (process-status proc))
> +                   (unwind-protect
> +                       (if (with-current-buffer source
> +                             (eq proc mhtml-ts-mode--flymake-process))
> +                           (with-current-buffer (process-buffer proc)
> +                             (goto-char (point-min))
> +                             (let (diags)
> +                               (while (search-forward-regexp diagnostics-pattern nil t)
> +                                 (let* ((pos
> +                                         (flymake-diag-region
> +                                          source
> +                                          (string-to-number (match-string 1))
> +                                          (string-to-number (match-string 2)))) ;; line and column
> +                                        (type (cond ((equal (match-string 3) "Warning") :warning)
> +                                                    ((equal (match-string 3) "Error") :error))) ;; type of message
> +                                        (msg (match-string 4))) ;; message
> +                                   (push (flymake-make-diagnostic source (car pos) (cdr pos) type msg)
> +                                         diags)))
> +                               (funcall report-fn diags)))
> +                         (flymake-log :warning "Canceling obsolete check %s" proc))
> +                     (kill-buffer (process-buffer proc)))))))
> +        (process-send-region mhtml-ts-mode--flymake-process (point-min) (point-max))
> +        (process-send-eof mhtml-ts-mode--flymake-process)))))
> +
> +(define-derived-mode mhtml-ts-mode html-ts-mode
> +  '("HTML+" (:eval (let ((lang (mhtml-ts-mode--language-at-point (point))))
> +                     (cond ((eq lang 'html) "")
> +                           ((eq lang 'javascript) "JS")
> +                           ((eq lang 'css) "CSS")))))
> +  "Major mode for editing HTML with embedded JavaScript and CSS.
> +Powered by tree-sitter."
> +  (if (not (and
> +            (treesit-ready-p 'html)
> +            (treesit-ready-p 'javascript)
> +            (treesit-ready-p 'css)))
> +      (error "Tree-sitter parsers for HTML isn't available.  You can
> +    install the parsers with M-x `mhtml-ts-mode-install-parsers'")
> +
> +    ;; When an language is embedded, you should initialize some variable
> +    ;; just like it's done in the original mode.
> +
> +    ;; Comment.
> +    ;; indenting settings for js-ts-mode.
> +    (c-ts-common-comment-setup)
> +    (setq-local comment-multi-line t)
> +
> +    ;; Font-lock.
> +
> +    ;; There are two ways to handle embedded code:
> +    ;; 1. Use a single parser for all the embedded code in the buffer. In
> +    ;; this case, the embedded code blocks are concatenated together and are
> +    ;; seen as a single continuous document to the parser.
> +    ;; 2. Each embedded code block gets its own parser. Each parser only sees
> +    ;; that particular code block.
> +
> +    ;; If you go with 2 for a language, the local parsers are created and
> +    ;; destroyed automatically by Emacs. So don't create a global parser for
> +    ;; that embedded language here.
> +
> +    ;; Create the parsers, only the global ones.
> +    ;; jsdoc is a local parser, don't create a parser for it.
> +    (treesit-parser-create 'css)
> +    (treesit-parser-create 'javascript)
> +
> +    ;; Multi-language modes must set the  primary parser.
> +    (setq-local treesit-primary-parser (treesit-parser-create 'html))
> +
> +    (setq-local treesit-range-settings
> +                (treesit-range-rules
> +                 :embed 'javascript
> +                 :host 'html
> +                 '((script_element
> +                    (start_tag (tag_name))
> +                    (raw_text) @cap))
> +
> +                 ;; Another rule could be added that when it matches an
> +                 ;; attribute_value that has as its parent an
> +                 ;; attribute_name "style" it captures it and then
> +                 ;; passes it to the css parser.
> +                 :embed 'css
> +                 :host 'html
> +                 '((style_element
> +                    (start_tag (tag_name))
> +                    (raw_text) @cap))))
> +
> +    ;; jsdoc is not mandatory for js-ts-mode, so we respect this by
> +    ;; adding jsdoc range rules only when jsdoc is available.
> +    (when (treesit-ready-p 'jsdoc t)
> +      (setq-local treesit-range-settings
> +                  (append treesit-range-settings
> +                          (treesit-range-rules
> +                           :embed 'jsdoc
> +                           :host 'javascript
> +                           :local t
> +                           `(((comment) @cap
> +                              (:match ,js--treesit-jsdoc-beginning-regexp @cap))))))
> +      (setq-local c-ts-common--comment-regexp
> +                  js--treesit-jsdoc-comment-regexp))
> +
> +
> +    ;; Many treesit fuctions need to know the language at-point.
> +    ;; So you should define such a function.
> +    (setq-local treesit-language-at-point-function #'mhtml-ts-mode--language-at-point)
> +    (setq-local prettify-symbols-alist mhtml-ts-mode--prettify-symbols-alist)
> +
> +    ;; Indent.
> +
> +    ;; Since `mhtml-ts-mode' inherits indentation rules from `html-ts-mode', `js-ts-mode'
> +    ;; and `css-ts-mode', if you want to change the offset you have to act on the
> +    ;; *-offset variables defined for those languages.
> +
> +    ;; JavaScript and CSS must be indented relative to their code block.
> +    ;; This is done by inserting a special rule before the normal
> +    ;; indentation rules of these languages.
> +    ;; The value of `mhtml-ts-mode--js-css-indent-offset' changes based on
> +    ;; `mhtml-ts-mode-tag-relative-indent' and can be used to indent
> +    ;; JavaScript and CSS code relative to the HTML that contains them,
> +    ;; just like in mhtml-mode.
> +    (setq-local treesit-simple-indent-rules mhtml-ts-mode--treesit-indent-rules)
> +
> +    ;; Navigation.
> +
> +    ;; This is for which-function-mode.
> +    ;; Since mhtml-ts-mode is derived from html-ts-mode, which sets
> +    ;; the value of `treesit-defun-type-regexp', you have to reset it to nil
> +    ;; otherwise `imenu' and `which-function-mode' will not work.
> +    (setq-local treesit-defun-type-regexp nil)
> +
> +    ;; This is for finding defun name, it's used by IMenu as default
> +    ;; function no specific functions are defined.
> +    (setq-local treesit-defun-name-function #'mhtml-ts-mode--defun-name)
> +
> +    ;; Define what are 'thing' for treesit.
> +    ;; 'Thing' is a symbol representing the thing, like `defun', `sexp', or
> +    ;; `sentence'.
> +    ;; As an alternative, if you want just defun, you can define a `treesit-defun-type-regexp'.
> +    (setq-local treesit-thing-settings mhtml-ts-mode--treesit-thing-settings)
> +
> +    ;; Font-lock.
> +
> +    ;; In a multi-language scenario, font lock settings are usually a
> +    ;; concatenation of language rules. As you can see, it is possible
> +    ;; to extend/modify the default rule or use a different set of
> +    ;; rules. See `php-ts-mode--custom-html-font-lock-settings' for more
> +    ;; advanced usage.
> +    (setq-local treesit-font-lock-settings mhtml-ts-mode--treesit-font-lock-settings)
> +
> +    ;; Tells treesit the list of features to fontify.
> +    (setq-local treesit-font-lock-feature-list mhtml-ts-mode--treesit-font-lock-feature-list)
> +
> +    ;; Imenu
> +
> +    ;; Setup Imenu: if no function is specified, try to find an object
> +    ;; using `treesit-defun-name-function'.
> +    (setq-local treesit-aggregated-simple-imenu-settings
> +                mhtml-ts-mode--treesit-aggregated-simple-imenu-settings)
> +
> +    (treesit-major-mode-setup)
> +
> +    ;; This is sort of a prog-mode as well as a text mode.
> +    (run-mode-hooks 'prog-mode-hook)
> +
> +    ;; Flymake
> +    (add-hook 'flymake-diagnostic-functions #'mhtml-ts-mode-flymake-mhtml nil 'local)))
> +
> +;; Add nome extra parents.
> +(derived-mode-add-parents 'mhtml-ts-mode '(css-mode js-mode))
> +
> +(when (and (treesit-ready-p 'html) (treesit-ready-p 'javascript) (treesit-ready-p 'css))
> +  (add-to-list
> +   'auto-mode-alist '("\\.[sx]?html?\\(\\.[a-zA-Z_]+\\)?\\'" . mhtml-ts-mode)))
> +
> +(provide 'mhtml-ts-mode)
> +;;; mhtml-ts-mode.el ends here
> diff --git a/lisp/treesit.el b/lisp/treesit.el
> index 8d86d142e3f..00b7d266e74 100644
> --- a/lisp/treesit.el
> +++ b/lisp/treesit.el
> @@ -1297,6 +1297,40 @@ treesit-font-lock-recompute-features
>                         ((memq feature remove-list) nil)
>                         (t current-value))))))
>  
> +(defun treesit-merge-font-lock-feature-list (features-list-1 features-list-2)
> +  "Merge two tree-sitter font lock feature lists.
> +Returns a new font lock feature list with no duplicates in the same level.
> +It can be used to merge font lock feature lists in a multi-language major mode.
> +FEATURES-LIST-1 and FEATURES-LIST-2 are list of lists of feature symbols."
> +    (let ((result nil)
> +	(features-1 (car features-list-1))
> +	(features-2 (car features-list-2)))
> +    (while (or features-1 features-2)
> +      (cond
> +       ((and features-1 (not features-2)) (push features-1 result))
> +       ((and (not features-1) features-2) (push features-2 result))
> +       ((and features-1 features-2) (push (cl-union features-1 features-2) result)))
> +      (setq features-list-1 (cdr features-list-1)
> +	    features-list-2 (cdr features-list-2)
> +	    features-1 (car features-list-1)
> +            features-2 (car features-list-2)))
> +    (nreverse result)))
> +
> +(defun treesit-replace-font-lock-feature-settings (new-settings settings)
> +  "Replaces :feature in SETTINGS with :feature from NEW-SETTINGS.
> +Both SETTINGS and NEW-SETTINGS must be a value suitable for
> +`treesit-font-lock-settings'.
> +Return a value suitable for `treesit-font-lock-settings'"
> +  (let ((result nil))
> +    (dolist (new-setting new-settings)
> +      (let ((new-feature (treesit-font-lock-setting-feature new-setting)))
> +	(dolist (setting settings)
> +	  (let ((feature (treesit-font-lock-setting-feature setting)))
> +	    (if (eq new-feature feature)
> +		(push new-setting result)
> +	      (push setting result))))))
> +    (nreverse result)))
> +
>  (defun treesit-add-font-lock-rules (rules &optional how feature)
>    "Add font-lock RULES to the current buffer.
>  
> @@ -2401,6 +2435,37 @@ treesit--indent-rules-optimize
>                              offset)))))
>               (cons lang (mapcar #'optimize-rule indent-rules)))))
>  
> +(defun treesit-modify-indent-rules (new-rules rules &optional how)
> +  "Modify RULES using NEW-RULES.
> +As default replace rules with the same anchor.
> +When HOW is :prepend NEW-RULES are prepend to RULES, when
> +:append NEW-RULES are appended to RULES, when :replace (the default)
> +NEW-RULES replace rule in RULES which the same anchor."
> +  (let ((n-lang (car (car new-rules)))
> +	(lang (car (car rules))))
> +    (when (not (eq n-lang lang))
> +      (error "The language must be the same"))
> +    (let* ((nr (cdr (car new-rules)))
> +           (r (cdr (car rules)))
> +           (tmp nil)
> +           (result
> +            (cond
> +             ((eq how :prepend)
> +	      (append nr r))
> +             ((eq how :append)
> +              (append r nr))
> +             ((or (eq how :replace) t)
> +              (nreverse
> +               (progn
> +                 (dolist (new-rule nr)
> +	           (dolist (rule r)
> +	             (if (equal (nth 0 new-rule) (nth 0 rule))
> +		         (push new-rule tmp)
> +		       (push rule tmp))))
> +                 tmp))))))
> +      (push lang result)
> +      `(,result))))
> +
>  ;;; Search
>  
>  (defun treesit-search-forward-goto
> -- 
> 2.48.1
> 




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 19 Jan 2025 17:09:47 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Jan 19 12:09:47 2025
Received: from localhost ([127.0.0.1]:47236 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tZYnx-0003Q7-Cu
	for submit <at> debbugs.gnu.org; Sun, 19 Jan 2025 12:09:47 -0500
Received: from mail-wm1-x32c.google.com ([2a00:1450:4864:20::32c]:52487)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <v.pupillo@HIDDEN>)
 id 1tZYns-0003Pr-St
 for 74610 <at> debbugs.gnu.org; Sun, 19 Jan 2025 12:09:43 -0500
Received: by mail-wm1-x32c.google.com with SMTP id
 5b1f17b1804b1-436341f575fso39938205e9.1
 for <74610 <at> debbugs.gnu.org>; Sun, 19 Jan 2025 09:09:40 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1737306575; x=1737911375; darn=debbugs.gnu.org;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:from:to:cc:subject:date
 :message-id:reply-to;
 bh=kQKwhRS4y0sGohoQWW90HkrtTDSnAstofCG0s61rQRk=;
 b=Jbr7rxUO1acK+9qA7SBkGuwXA+rwmqTDIUX0BLDDae+XV9+RMXRjWfZtaa/76kbCiv
 7Fq7nMmTmypyp2DcL3mCUROwqd/50+y3ABtCgartOqsBqYD60o0YFOsOFxCRJeQJf+Ux
 tAncMOHHWVL20p58TvRwK6MJ6GijEdTS1PL0fY74yco9ibWLdTpNrrOqW7PD3REWdpzD
 ySjUVqf11FnSVhvUSf3wO6bHDUF6ps1PuF0n8qfjwtx7jJjALB9ABve4EwXJxVIIiGS4
 jzreP6SMtil15r1iOsJ/5ettyHVrFdy2NcQXLikCuJ3xWnQOV7oGTQc+PILZ80tFdAJM
 kzSQ==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1737306575; x=1737911375;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=kQKwhRS4y0sGohoQWW90HkrtTDSnAstofCG0s61rQRk=;
 b=GgqTjhXd/31ZhLvons76t++9IVebYwHnTt2Y2jZGIR12ulGVsJYhLZ2dvu+0NXcl0v
 ZX7yxO0M+mCqlBk6s0a4VWk3mxFp+pwsQ9+Iy74aO7tiCE+KWrbDfmilwcItaFQ5UJjg
 4H0moPiofL9TY479isVoMC5u7IDFrg+yGWCsbLcdI3wahEgUXFJM+azo5Zi1VmpbJRxK
 jnYMCinN9NEeN+mB+/QfcldeRB/DlH9hke0kA5Pxs9bjaCcCowq+w2lqw/xr9+8t0YdR
 LSCZyjNpO3js+s7WOD6DICy6sPWkNe3dUbTGLqpGfFUMKQ0PUKcYlA7wfxqSSDsxt8m5
 YnDg==
X-Forwarded-Encrypted: i=1;
 AJvYcCWv5iLvEocmecQFJ/IMqK6Jzbum1qMTN0u+8xJDFVr4n1e7CnVzy1m59ScJ8GiBIkQiyWpRuw==@debbugs.gnu.org
X-Gm-Message-State: AOJu0YwD0m6xqP8GsexopQ8Nz1Bnq9rO5roRkuk4NbijXklwTSk5Wf03
 2YbfDofwUKEiuWAnymx7lQf3+dEgCG8f+LrFJqOyWA8b4VgB1Qbf
X-Gm-Gg: ASbGncvqa9+NB2egY0MiHoFr6McsXCy1f3jUTJFJl11MKynCW6w5orPL2qKo2aOGuMf
 q8FfukNYmOPdqggSbvsxlYJdSYQthzveQOQLt6qfgjHjDt9R/pwsG/IQRWNWJ7XlF+5zg6B7g0x
 x2leiG/vkevJLxX+zSCELWD+86BK/cYSy//e3wPxtLs+MKNehW5HP5B3GViU7qXnvNSkL5g/RI5
 2EdF3Ll80xjawNZOaLRK0Y4kMBly6WgA/hFdnr0/4rwjS3yYmJceFlloCw11fpms52wFEBFWani
 2ArwhJ9G+7nw16O62RhrfPTcapWK2tm5dA==
X-Google-Smtp-Source: AGHT+IFZIL6eMBUJFaKCWQqESLFtP2kdrRq0k4sh8TExBAzVFApMF0R006SiIkTJ4YSLq3LrBDou5g==
X-Received: by 2002:a05:600c:3d96:b0:434:a26c:8291 with SMTP id
 5b1f17b1804b1-4389143b5dbmr85606385e9.24.1737306574245; 
 Sun, 19 Jan 2025 09:09:34 -0800 (PST)
Received: from fedora.localnet (2-230-139-124.ip202.fastwebnet.it.
 [2.230.139.124]) by smtp.gmail.com with ESMTPSA id
 5b1f17b1804b1-437c7528076sm169696585e9.25.2025.01.19.09.09.32
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Sun, 19 Jan 2025 09:09:32 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: Yuan Fu <casouri@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Sun, 19 Jan 2025 18:09:31 +0100
Message-ID: <26756353.1r3eYUQgxm@fedora>
In-Reply-To: <2ABDEC5D-B17E-4DD3-9371-48B61026A144@HIDDEN>
References: <3532547.LZWGnKmheA@fedora> <2462456.cojqenx9y0@fedora>
 <2ABDEC5D-B17E-4DD3-9371-48B61026A144@HIDDEN>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="nextPart2311485.vFx2qVVIhK"
Content-Transfer-Encoding: 7Bit
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org,
 Juri Linkov <juri@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This is a multi-part message in MIME format.

--nextPart2311485.vFx2qVVIhK
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"

Ciao Yuan,
In data sabato 18 gennaio 2025 02:37:53 Ora standard dell=E2=80=99Europa ce=
ntrale,=20
Yuan Fu ha scritto:
> > On Jan 14, 2025, at 1:41=E2=80=AFPM, Vincenzo Pupillo <v.pupillo@gmail.=
com> wrote:
> >=20
> > Ciao Yuan and Juri,
> > this is an updated version of mhtml-ts-mode.
> > I have tried to reduce as much as possible copies of parts of the major
> > modes from which it is derived.
> > To do this, I had to move some values that were assigned directly to
> > treesit's own variables  (in ccs-mode.el, in js.el, and in
> > html-ts-mode.el) into new variables.
> > I also added three new functions to treesit.el to make it easier to
> > combine
> > parts derived from the other major modes. So now any changes to these n=
ew
> > variables are directly reflected in the behavior of mhtml-ts-mode.
> > There are a few things I would like to highlight:
> > 1. treesit-font-lock-feature-lists are not defined per parser, so simply
> > merging the different lists will cause display differences from the
> > original major-modes; for example =E2=80=9Cfunction=E2=80=9D is defined=
 at level 3 in
> > css-ts-mode but at level 4 in js-ts-mode.
>=20
> Yeah, that=E2=80=99s not pretty, I don=E2=80=99t have a good solution for=
 now. Technically a
> major mode can use treesit-font-lock-recompute-features after
> treesit-major-mode-setup to do fine adjustments, but it=E2=80=99s ugly.
> > 2. treesit-defun-type-regexp has the same problem as treesit-font-lock-
> > feature-list, so I had to define it myself.
>=20
> Maybe you can use treesit-thing-settings instead?
Ok, done.

>=20
> > But other than that, it works pretty well.
> >=20
> > IMHO, a global "list" where you can define "font-lock", "indent-list",
> > "font- lock-feature", etc. by language (perhaps with getter and setter
> > methods) might make it easier to define new multilingual major-modes. It
> > could improve the decoupling between multilingual major-modes and the
> > major-modes they are derived from. It could also better decouple the
> > internal implementation of treesit.el from the treesitter-based
> > major-modes.
> >=20
> > Let me know what you think.
>=20
> That=E2=80=99s what I had in mind. That=E2=80=99s also why I added a new =
variable
> treesit-aggregated-simple-imenu-settings instead of piggybacking on
> treesit-simple-imenu-settings. A separate variable is simpler to borrow
> settings from.
Yes, in this new patch I used treesit-aggregated-simple-imenu-settings.

> But I didn=E2=80=99t have time to think it though yet. (By language, or b=
y mode and
> language? Should packages set it on load, or define a function that loads
> these settings? Etc.)
>
I would say by mode and language, both mhtml-ts mode and html-ts mode have =
the=20
same primary parser. For the second question, I don't know. I don't have=20
enough experience with it to know what the pros and cons are.

> Yuan

Vincenzo

--nextPart2311485.vFx2qVVIhK
Content-Disposition: attachment; filename="0001-Add-mhtml-ts-mode.patch"
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-patch; charset="UTF-8";
 name="0001-Add-mhtml-ts-mode.patch"

=46rom 199af27f1d761d100f6a8a6163c27e35e6ab2ea0 Mon Sep 17 00:00:00 2001
=46rom: Vincenzo Pupillo <v.pupillo@HIDDEN>
Date: Sun, 19 Jan 2025 17:49:30 +0100
Subject: [PATCH] Add mhtml-ts-mode.

New major-mode alternative to mhtml-mode, based on treesitter, for
editing files containing html, javascript and css.

* etc/NEWS: Mention the new mode and new functions.
* lisp/textmodes/mhtml-ts-mode.el: New file.
* lisp/progmodes/js.el (js--treesit-thing-settings): New variable.
(js--treesit-font-lock-feature-list); New variable.
(js--treesit-simple-imenu-settings): New variable.
(js--treesit-defun-type-regexp): New variable.
(js--treesit-jsdoc-comment-regexp): New variable.
(js-ts-mode): Use of new variables instead of direct assignment of
values.
* lisp/textmodes/css-mode.el (css-mode--menu): New variable.
(css-mode-map): Use new variable.
(css--treesit-font-lock-feature-list): New variable.
(css--treesit-simple-imenu-settings): New variable.
(css--treesit-defun-type-regexp): New variable.
(cs-ts-mode): Use of new variables instead of direct assignment of
values.
* lisp/textmodes/html-ts-mode.el
(html-ts-mode--treesit-things-settings): New variable.
(html-ts-mode--treesit-font-lock-feature-list): New variable.
(html-ts-mode--treesit-simple-imenu-settings): New variable.
(html-ts-mode--treesit-defun-type-regexp): New variable.
(html-ts-mode): Use of new variables instead of direct assignment of
values.
* lisp/treesit.el
(treesit-merge-font-lock-feature-list): New fuction.
(treesit-replace-font-lock-feature-settings): New fuction.
(treesit-modify-indent-rules): New function.
=2D--
 etc/NEWS                        |  28 ++
 lisp/progmodes/js.el            |  72 ++--
 lisp/textmodes/css-mode.el      |  47 ++-
 lisp/textmodes/html-ts-mode.el  |  45 ++-
 lisp/textmodes/mhtml-ts-mode.el | 600 ++++++++++++++++++++++++++++++++
 lisp/treesit.el                 |  65 ++++
 6 files changed, 798 insertions(+), 59 deletions(-)
 create mode 100644 lisp/textmodes/mhtml-ts-mode.el

diff --git a/etc/NEWS b/etc/NEWS
index 0b849dec450..6360a082e9f 100644
=2D-- a/etc/NEWS
+++ b/etc/NEWS
@@ -965,6 +965,12 @@ destination window is chosen using 'display-buffer-ali=
st'.  Example:
=20
 =0C
 * New Modes and Packages in Emacs 31.1
+** New major modes based on the tree-sitter library
+
+*** New major mode 'mhtml-ts-mode'.
+An optional major mode based on the tree-sitter library for editing html
+files. This mode handles indentation, fontification, and commenting for
+embedded JavaScript and CSS.
=20
 =0C
 * Incompatible Lisp Changes in Emacs 31.1
@@ -1098,6 +1104,28 @@ language symbol.  For example, 'cpp' is translated t=
o "C++".  A new
 variable 'treesit-language-display-name-alist' holds the translations of
 language symbols where that translation is not trivial.
=20
++++
+*** New function 'treesit-merge-font-lock-feature-list'.
+This function the merge two tree-sitter font lock feature lists.
+Returns a new font lock feature list with no duplicates in the same level.
+It can be used to merge font lock feature lists in a multi-language major =
mode.
+
++++
+*** New function 'treesit-replace-font-lock-feature-settings'.
+Given two treesit-font-lock-settings replaces the feature in the second
+font-lock-settings with the same feature in the first
+font-lock-settings. In a multi-linguage major mode it is sometimes
+necessary to replace features from one of the major modes, with others
+that are better suited to the new multilingual context.
+
++++
+*** New function 'treesit-modify-indent-rules'.
+Given two treesit ident rules, it replaces, adds, or prepends the new
+rules to the old ones, then returns a new treesit indent rules.
+In a multi-linguage major mode it is sometimes necessary to modify rules
+from one of the major modes, with others that are better suited to the
+new multilingual context.
+
 +++
 *** New command 'treesit-explore'
 This command replaces 'treesit-explore-mode'.  It turns on
diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
index 101b882c718..be9d6f64aef 100644
=2D-- a/lisp/progmodes/js.el
+++ b/lisp/progmodes/js.el
@@ -3901,6 +3901,44 @@ js--treesit-list-nodes
 (defvar js--treesit-jsdoc-beginning-regexp (rx bos "/**")
   "Regular expression matching the beginning of a jsdoc block comment.")
=20
+(defvar js--treesit-thing-settings
+  `((javascript
+     (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
+     (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
+     (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
+     (text ,(js--regexp-opt-symbol '("comment"
+                                     "string_fragment")))))
+  "Settings for `treesit-thing-settings'.")
+
+(defvar js--treesit-font-lock-feature-list
+  '(( comment document definition)
+    ( keyword string)
+    ( assignment constant escape-sequence jsx number
+      pattern string-interpolation)
+    ( bracket delimiter function operator property))
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar js--treesit-simple-imenu-settings
+  `(("Function" "\\`function_declaration\\'" nil nil)
+    ("Variable" "\\`lexical_declaration\\'"
+     js--treesit-valid-imenu-entry nil)
+    ("Class" ,(rx bos (or "class_declaration"
+                          "method_definition")
+                  eos)
+     nil nil))
+  "Settings for `treesit-simple-imenu'.")
+
+(defvar js--treesit-defun-type-regexp
+  (rx (or "class_declaration"
+          "method_definition"
+          "function_declaration"
+          "lexical_declaration"))
+  "Settings for `treesit-defun-type-regexp'.")
+
+(defvar js--treesit-jsdoc-comment-regexp
+  (rx (or "comment" "line_comment" "block_comment" "description"))
+  "Regexp for `c-ts-common--comment-regexp'.")
+
 ;;;###autoload
 (define-derived-mode js-ts-mode js-base-mode "JavaScript"
   "Major mode for editing JavaScript.
@@ -3931,29 +3969,15 @@ js-ts-mode
     ;; Indent.
     (setq-local treesit-simple-indent-rules js--treesit-indent-rules)
     ;; Navigation.
=2D    (setq-local treesit-defun-type-regexp
=2D                (rx (or "class_declaration"
=2D                        "method_definition"
=2D                        "function_declaration"
=2D                        "lexical_declaration")))
+    (setq-local treesit-defun-type-regexp js--treesit-defun-type-regexp)
+
     (setq-local treesit-defun-name-function #'js--treesit-defun-name)
=20
=2D    (setq-local treesit-thing-settings
=2D                `((javascript
=2D                   (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
=2D                   (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
=2D                   (sentence ,(js--regexp-opt-symbol js--treesit-sentenc=
e-nodes))
=2D                   (text ,(js--regexp-opt-symbol '("comment"
=2D                                                   "string_fragment"))))=
))
+    (setq-local treesit-thing-settings js--treesit-thing-settings)
=20
     ;; Fontification.
     (setq-local treesit-font-lock-settings js--treesit-font-lock-settings)
=2D    (setq-local treesit-font-lock-feature-list
=2D                '(( comment document definition)
=2D                  ( keyword string)
=2D                  ( assignment constant escape-sequence jsx number
=2D                    pattern string-interpolation)
=2D                  ( bracket delimiter function operator property)))
+    (setq-local treesit-font-lock-feature-list js--treesit-font-lock-featu=
re-list)
=20
     (when (treesit-ready-p 'jsdoc t)
       (setq-local treesit-range-settings
@@ -3963,17 +3987,11 @@ js-ts-mode
                    :local t
                    `(((comment) @capture (:match ,js--treesit-jsdoc-beginn=
ing-regexp @capture)))))
=20
=2D      (setq c-ts-common--comment-regexp (rx (or "comment" "line_comment"=
 "block_comment" "description"))))
+      (setq c-ts-common--comment-regexp js--treesit-jsdoc-comment-regexp))
=20
     ;; Imenu
=2D    (setq-local treesit-simple-imenu-settings
=2D                `(("Function" "\\`function_declaration\\'" nil nil)
=2D                  ("Variable" "\\`lexical_declaration\\'"
=2D                   js--treesit-valid-imenu-entry nil)
=2D                  ("Class" ,(rx bos (or "class_declaration"
=2D                                        "method_definition")
=2D                                eos)
=2D                   nil nil)))
+    (setq-local treesit-simple-imenu-settings js--treesit-simple-imenu-set=
tings)
+
     (treesit-major-mode-setup)
=20
     (add-to-list 'auto-mode-alist
diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index 53340195386..35c61e4f66d 100644
=2D-- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -893,13 +893,7 @@ css-mode-syntax-table
     (modify-syntax-entry ?? "." st)
     st))
=20
=2D(defvar-keymap css-mode-map
=2D  :doc "Keymap used in `css-mode'."
=2D  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
=2D  ;; `info-complete-symbol' is not used.
=2D  "<remap> <complete-symbol>" #'completion-at-point
=2D  "C-c C-f" #'css-cycle-color-format
=2D  :menu
+(defvar css-mode--menu
   '("CSS"
     :help "CSS-specific features"
     ["Reformat block" fill-paragraph
@@ -910,7 +904,17 @@ css-mode-map
     ["Describe symbol" css-lookup-symbol
      :help "Display documentation for a CSS symbol"]
     ["Complete symbol" completion-at-point
=2D     :help "Complete symbol before point"]))
+     :help "Complete symbol before point"])
+    "Menu bar for `css-mode'")
+
+(defvar-keymap css-mode-map
+  :doc "Keymap used in `css-mode'."
+  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
+  ;; `info-complete-symbol' is not used.
+  "<remap> <complete-symbol>" #'completion-at-point
+  "C-c C-f" #'css-cycle-color-format
+  :menu
+  css-mode--menu)
=20
 (eval-and-compile
   (defconst css--uri-re
@@ -1771,6 +1775,21 @@ css--extract-index-name
               (replace-regexp-in-string "[\n ]+" " " s)))
            res)))))))
=20
+(defvar css--treesit-font-lock-feature-list
+  '((selector comment query keyword)
+    (property constant string)
+    (error variable function operator bracket))
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar css--treesit-simple-imenu-settings
+  `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
+      nil nil))
+  "Settings for `treesit-simple-imenu'.")
+
+(defvar css--treesit-defun-type-regexp
+  "rule_set"
+  "Settings for `treesit-defun-type-regexp'.")
+
 (define-derived-mode css-base-mode prog-mode "CSS"
   "Generic mode to edit Cascading Style Sheets (CSS).
=20
@@ -1825,16 +1844,12 @@ css-ts-mode
     ;; Tree-sitter specific setup.
     (setq treesit-primary-parser (treesit-parser-create 'css))
     (setq-local treesit-simple-indent-rules css--treesit-indent-rules)
=2D    (setq-local treesit-defun-type-regexp "rule_set")
+    (setq-local treesit-defun-type-regexp css--treesit-defun-type-regexp)
     (setq-local treesit-defun-name-function #'css--treesit-defun-name)
     (setq-local treesit-font-lock-settings css--treesit-settings)
=2D    (setq-local treesit-font-lock-feature-list
=2D                '((selector comment query keyword)
=2D                  (property constant string)
=2D                  (error variable function operator bracket)))
=2D    (setq-local treesit-simple-imenu-settings
=2D                `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
=2D                    nil nil)))
+    (setq-local treesit-font-lock-feature-list css--treesit-font-lock-feat=
ure-list)
+    (setq-local treesit-simple-imenu-settings css--treesit-simple-imenu-se=
ttings)
+
     (treesit-major-mode-setup)
=20
     (add-to-list 'auto-mode-alist '("\\.css\\'" . css-ts-mode))))
diff --git a/lisp/textmodes/html-ts-mode.el b/lisp/textmodes/html-ts-mode.el
index dad49b7ed4c..5700c2b406b 100644
=2D-- a/lisp/textmodes/html-ts-mode.el
+++ b/lisp/textmodes/html-ts-mode.el
@@ -87,6 +87,31 @@ html-ts-mode--font-lock-settings
    `((attribute_name) @font-lock-variable-name-face))
   "Tree-sitter font-lock settings for `html-ts-mode'.")
=20
+(defvar html-ts-mode--treesit-things-settings
+  `((html
+     (sexp ,(regexp-opt '("element"
+                          "text"
+                          "attribute"
+                          "value")))
+     (list ,(regexp-opt '("element")) 'symbols)
+     (sentence "tag")
+     (text ,(regexp-opt '("comment" "text")))))
+  "Settings for `treesit-thing-settings'.")
+
+(defvar html-ts-mode--treesit-font-lock-feature-list
+  '((comment keyword definition)
+    (property string)
+    () ())
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar html-ts-mode--treesit-simple-imenu-settings
+  '(("Element" "\\`tag_name\\'" nil nil))
+  "Settings for `treesit-simple-imenu'.")
+
+(defvar html-ts-mode--treesit-defun-type-regexp
+  "element"
+  "Settings for `treesit-defun-type-regexp'.")
+
 (defun html-ts-mode--defun-name (node)
   "Return the defun name of NODE.
 Return nil if there is no name or if NODE is not a defun node."
@@ -107,30 +132,18 @@ html-ts-mode
   (setq-local treesit-simple-indent-rules html-ts-mode--indent-rules)
=20
   ;; Navigation.
=2D  (setq-local treesit-defun-type-regexp "element")
+  (setq-local treesit-defun-type-regexp html-ts-mode--treesit-defun-type-r=
egexp)
=20
   (setq-local treesit-defun-name-function #'html-ts-mode--defun-name)
=20
=2D  (setq-local treesit-thing-settings
=2D              `((html
=2D                 (sexp ,(regexp-opt '("element"
=2D                                      "text"
=2D                                      "attribute"
=2D                                      "value")))
=2D                 (list ,(regexp-opt '("element")) 'symbols)
=2D                 (sentence "tag")
=2D                 (text ,(regexp-opt '("comment" "text"))))))
+  (setq-local treesit-thing-settings html-ts-mode--treesit-things-settings)
=20
   ;; Font-lock.
   (setq-local treesit-font-lock-settings html-ts-mode--font-lock-settings)
=2D  (setq-local treesit-font-lock-feature-list
=2D              '((comment keyword definition)
=2D                (property string)
=2D                () ()))
+  (setq-local treesit-font-lock-feature-list html-ts-mode--treesit-font-lo=
ck-feature-list)
=20
   ;; Imenu.
=2D  (setq-local treesit-simple-imenu-settings
=2D              '(("Element" "\\`tag_name\\'" nil nil)))
+  (setq-local treesit-simple-imenu-settings html-ts-mode--treesit-simple-i=
menu-settings)
=20
   ;; Outline minor mode.
   (setq-local treesit-outline-predicate "\\`element\\'")
diff --git a/lisp/textmodes/mhtml-ts-mode.el b/lisp/textmodes/mhtml-ts-mode=
=2Eel
new file mode 100644
index 00000000000..272d00a1ef6
=2D-- /dev/null
+++ b/lisp/textmodes/mhtml-ts-mode.el
@@ -0,0 +1,600 @@
+;;; mhtml-ts-mode.el --- Major mode for HTML using tree-sitter -*- lexical=
=2Dbinding: t; -*-
+
+;; Copyright (C) 2024 Free Software Foundation, Inc.
+
+;; Author: Vincenzo Pupillo <v.pupillo@HIDDEN>
+;; Maintainer: Vincenzo Pupillo <v.pupillo@HIDDEN>
+;; Created: Nov 2024
+;; Keywords: HTML languages hypermedia tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; This package provides `mhtml-ts-mode' which is a major mode
+;; for editing HTML files with embedded JavaScript and CSS.
+;; Tree Sitter is used to parse each of these languages.
+;;
+;; Please note that this package requires `html-ts-mode', which
+;; registers itself as the major mode for editing HTML.
+;;
+;; This package is compatible and has been tested with the following
+;; tree-sitter grammars:
+;; * https://github.com/tree-sitter/tree-sitter-html
+;; * https://github.com/tree-sitter/tree-sitter-javascript
+;; * https://github.com/tree-sitter/tree-sitter-jsdoc
+;; * https://github.com/tree-sitter/tree-sitter-css
+;;
+;; Features
+;;
+;; * Indent
+;; * Flymake
+;; * IMenu
+;; * Navigation
+;; * Which-function
+;; * Tree-sitter parser installation helper
+
+;;; Code:
+
+(require 'treesit)
+(require 'html-ts-mode)
+(require 'css-mode) ;; for embed css into html
+(require 'js) ;; for embed javascript into html
+
+(eval-when-compile
+  (require 'rx))
+
+;; This tells the byte-compiler where the functions are defined.
+;; Is only needed when a file needs to be able to byte-compile
+;; in a Emacs not built with tree-sitter library.
+(treesit-declare-unavailable-functions)
+
+;; In a multi-language major mode can be useful to have an "installer" to
+;; simplify the installation of the grammars supported by the major-mode.
+(defvar mhtml-ts-mode--language-source-alist
+  '((html . ("https://github.com/tree-sitter/tree-sitter-html"  "v0.23.2"))
+    (javascript . ("https://github.com/tree-sitter/tree-sitter-javascript"=
 "v0.23.1"))
+    (jsdoc . ("https://github.com/tree-sitter/tree-sitter-jsdoc" "v0.23.2"=
))
+    (css . ("https://github.com/tree-sitter/tree-sitter-css" "v0.23.1")))
+  "Treesitter language parsers required by `mhtml-ts-mode'.
+You can customize this variable if you want to stick to a specific
+commit and/or use different parsers.")
+
+(defun mhtml-ts-mode-install-parsers ()
+  "Install all the required treesitter parsers.
+`mhtml-ts-mode--language-source-alist' defines which parsers to install."
+  (interactive)
+  (let ((treesit-language-source-alist mhtml-ts-mode--language-source-alis=
t))
+    (dolist (item mhtml-ts-mode--language-source-alist)
+      (treesit-install-language-grammar (car item)))))
+
+;;; Custom variables
+
+(defgroup mhtml-ts-mode nil
+  "Major mode for editing HTML files, based on `html-ts-mode'.
+Works with JS and CSS and for that use `js-ts-mode' and `css-ts-mode'."
+  :prefix "mhtml-ts-mode-"
+  ;; :group 'languages
+  :group 'html)
+
+(defcustom mhtml-ts-mode-js-css-indent-offset 2
+  "JavaScript and CSS indent spaces related to the <script> and <style> HT=
ML tags.
+By default should have same value as `html-ts-mode-indent-offset'."
+  :tag "HTML javascript or css indent offset"
+  :version "31.1"
+  :type 'integer
+  :safe 'integerp)
+
+(defcustom mhtml-ts-mode-pretty-print-command
+  ;; prefer tidy because it's used by sgml-mode
+  (let ((executable nil))
+    (cond ((setq executable (executable-find "tidy"))
+           (format
+            "%s --gnu-emacs yes --wrap 0 --indent-spaces %s -q -i -"
+            executable html-ts-mode-indent-offset))
+          ((setq executable (executable-find "xmllint"))
+           (format "%s --html --quiet --format -" executable))
+          (t "Install tidy, ore some other HTML pretty print tool, and set=
 `mhtml-ts-mode-pretty-print-command'.")))
+  "The command to pretty print the current HTML buffer."
+  :type 'string
+  :version "31.1")
+
+(defvar mhtml-ts-mode--js-css-indent-offset
+  mhtml-ts-mode-js-css-indent-offset
+  "Internal copy of `mhtml-ts-mode-js-css-indent-offset'.
+The value changes, by `mhtml-ts-mode--tag-relative-indent-offset' accordin=
g to
+the value of `mhtml-ts-mode-tag-relative-indent'.")
+
+(defun mhtml-ts-mode--tag-relative-indent-offset (sym val)
+  "Custom setter for `mhtml-ts-mode-tag-relative-indent'.
+Apart from setting the default value of SYM to VAL, also change the
+value of SYM in `mhtml-ts-mode' buffers to VAL.  SYM should be
+`mhtml-ts-mode-tag-relative-indent', and VAL should be t, nil or
+`ignore'.  When sym is `mhtml-ts-mode-tag-relative-indent' set the
+value of `mhtml-ts-mode--js-css-indent-offset' to 0 if VAL is t,
+otherwise to `mhtml-ts-mode-js-css-indent-offset'."
+  (set-default sym val)
+  (when (eq sym 'mhtml-ts-mode-tag-relative-indent)
+    (setq
+     mhtml-ts-mode--js-css-indent-offset
+     (if (eq val t)
+         mhtml-ts-mode-js-css-indent-offset
+       0))))
+
+(defcustom mhtml-ts-mode-tag-relative-indent t
+  "How <script> and <style> bodies are indented relative to the tag.
+
+When t, indentation looks like:
+
+  <script>
+    code();
+  </script>
+
+When nil, indentation of the tag body starts just below the
+tag, like:
+
+  <script>
+  code();
+  </script>
+
+When `ignore', the tag body starts in the first column, like:
+
+  <script>
+code();
+  </script>"
+  :type '(choice (const nil) (const t) (const ignore))
+  :safe 'symbolp
+  :set #'mhtml-ts-mode--tag-relative-indent-offset
+  :version "31.1")
+
+(defcustom mhtml-ts-mode-css-fontify-colors t
+  "Whether CSS colors should be fontified using the color as the backgroun=
d.
+If non-nil, text representing a CSS color will be fontified
+such that its background is the color itself.
+Works like `css--fontify-region'."
+  :tag "HTML colors the CSS properties values."
+  :version "31.1"
+  :type 'boolean
+  :safe 'booleanp)
+
+(defvar mhtml-ts-mode-saved-pretty-print-command nil
+  "The command last used to pretty print in this buffer.")
+
+(defun mhtml-ts-mode-pretty-print (command)
+  "Prettify the current buffer.
+Argument COMMAND The command to use."
+  (interactive
+   (list (read-string
+          "Prettify command: "
+          (or mhtml-ts-mode-saved-pretty-print-command
+              (concat mhtml-ts-mode-pretty-print-command " ")))))
+  (setq mhtml-ts-mode-saved-pretty-print-command command)
+  (save-excursion
+    (shell-command-on-region
+     (point-min) (point-max)
+     command (buffer-name) t
+     "*mhtml-ts-mode-pretty-pretty-print-errors*" t)))
+
+(defun mhtml-ts-mode--switch-fill-defun (&rest arguments)
+  "Switch between `fill-paragraph' and `prog-fill-reindent-defun'.
+In an HTML region it calls `fill-paragraph' as does `html-ts-mode',
+otherwise it calls `prog-fill-reindent-defun'.
+Optional ARGUMENTS to to be passed to it."
+  (interactive)
+  (if (eq (treesit-language-at (point)) 'html)
+      (funcall-interactively #'fill-paragraph arguments)
+    (funcall-interactively #'prog-fill-reindent-defun arguments)))
+
+(defvar-keymap mhtml-ts-mode-map
+  :doc "Keymap for `mhtml-ts-mode' buffers."
+  :parent html-mode-map
+  ;; `mhtml-ts-mode' derive from `html-ts-mode' so the keymap is the
+  ;; same, we need to add some mapping from others languages.
+  "C-c C-f" #'css-cycle-color-format
+  "M-q" #'mhtml-ts-mode--switch-fill-defun)
+
+;; Place the CSS menu in the menu bar as well.
+(easy-menu-define mhtml-ts-mode-menu mhtml-ts-mode-map
+  "Menu bar for `mhtml-ts-mode'."
+  css-mode--menu)
+
+;; Not used at the moment.
+(defun mthml-ts-mode--js-language-at-point (point)
+  "Return the language at POINT assuming the point is within a Javascript =
region."
+  (let* ((node (treesit-node-at point 'javascript))
+         (node-type (treesit-node-type node))
+         (node-start (treesit-node-start node))
+         (node-end (treesit-node-end node)))
+    (if (not (treesit-ready-p 'jsdoc t))
+        'javascript
+      (if (equal node-type "comment")
+          (save-excursion
+            ;; (message "node start =3D %s , end =3D %s" node-start node-e=
nd)
+            (goto-char node-start)
+            (if (search-forward "/**" node-end t)
+                'jsdoc
+              'javascript))
+        'javascript))))
+
+;; To enable some basic treesiter functionality, you should define
+;; a function that recognizes which grammar is used at-point.
+;; This function should be assigned to `treesit-language-at-point-function'
+(defun mhtml-ts-mode--language-at-point (point)
+  "Return the language at POINT assuming the point is within a HTML buffer=
=2E"
+  (let* ((node (treesit-node-at point 'html))
+         (parent (treesit-node-parent node))
+         (node-query (format "(%s (%s))"
+                             (treesit-node-type parent)
+                             (treesit-node-type node))))
+    (cond
+     ((equal "(script_element (raw_text))" node-query) 'javascript)
+     ;; ((equal "(script_element (raw_text))" node-query) 'mthml-ts-mode--=
js-language-at-point)
+     ((equal "(style_element (raw_text))" node-query) 'css)
+     (t 'html))))
+
+;; Custom font-lock function that's used to apply color to css color
+;; The signature of the function should be conforming to signature
+;; QUERY-SPEC required by `treesit-font-lock-rules'.
+(defun mhtml-ts-mode--colorize-css-value (node override start end &rest _)
+  "Colorize CSS property value like `css--fontify-region'.
+For NODE, OVERRIDE, START, and END, see `treesit-font-lock-rules'."
+  (if (and mhtml-ts-mode-css-fontify-colors
+           (string-equal "plain_value" (treesit-node-type node)))
+      (let ((color (css--compute-color start (treesit-node-text node t))))
+        (when color
+          (with-silent-modifications
+            (add-text-properties
+             (treesit-node-start node) (treesit-node-end node)
+             (list 'face (list :background color
+                               :foreground (readable-foreground-color
+                                            color)
+                               :box '(:line-width -1)))))))
+    (treesit-fontify-with-override
+     (treesit-node-start node) (treesit-node-end node)
+     'font-lock-variable-name-face
+     override start end)))
+
+;; Embedded languages =E2=80=8B=E2=80=8Bshould be indented according to th=
e language
+;; that embeds them.
+;; This function signature complies with `treesit-simple-indent-rules'
+;; ANCHOR.
+(defun mhtml-ts-mode--js-css-tag-bol (_node _parent &rest _)
+  "Find the first non-space characters of html tags <script> or <style>.
+Return `line-beginning-position' when `treesit-node-at' is html, or
+`mhtml-ts-mode-tag-relative-indent' is equal to ignore.
+NODE and PARENT are ignored."
+  (if (or (eq (treesit-language-at (point)) 'html)
+          (eq mhtml-ts-mode-tag-relative-indent 'ignore))
+      (line-beginning-position)
+    ;; Ok, we are in js or css block.
+    (save-excursion
+      (re-search-backward "<script.*>\\|<style.*>" nil t))))
+
+;; Treesit supports 4 level of decoration, `treesit-font-lock-level'
+;; define which level to use.  Major modes categorize their fontification
+;; features, these categories are defined by `treesit-font-lock-rules' of
+;; each major-mode using :feature keyword.
+;; In a multiple language Major mode it's a good idea to provide, for each
+;; level, the union of the :feature of the same level.
+;; TODO: Since the feature-list is not defined per "parser" (like, for
+;; example, the thing-settings), the same feature can appear in
+;; different levels, so the appearance of a multiple main mode can be
+;; different from the main mode used.  For e.g the feature "function" is
+;; at level 4 for Javascript while it is at level 3 for CSS.
+(defvar mhtml-ts-mode--treesit-font-lock-feature-list
+  (treesit-merge-font-lock-feature-list
+   html-ts-mode--treesit-font-lock-feature-list
+   (treesit-merge-font-lock-feature-list
+    js--treesit-font-lock-feature-list
+    css--treesit-font-lock-feature-list))
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar mhtml-ts-mode--treesit-font-lock-settings
+  (append html-ts-mode--font-lock-settings
+          js--treesit-font-lock-settings
+          ;; Let's replace a css rule with a new one that adds color to
+          ;; the css value.
+          (treesit-replace-font-lock-feature-settings
+           (treesit-font-lock-rules
+            :language 'css
+            :override t
+            :feature 'variable
+            '((plain_value) @font-lock-variable-name-face
+              (plain_value) @mhtml-ts-mode--colorize-css-value))
+           css--treesit-settings))
+  "Settings for `treesit-font-lock-settings'.")
+
+(defvar mhtml-ts-mode--treesit-thing-settings
+  ;; In addition to putting together the various definitions, we need to
+  ;; add 'defun' which is used to support `imenu' and 'which-function'.
+  (list
+   ;; HTML thing settings
+   (append
+    (car html-ts-mode--treesit-things-settings)
+    `((defun ,(regexp-opt (list html-ts-mode--treesit-defun-type-regexp)))=
))
+   ;; Javascript thing settings
+   (append
+    (car js--treesit-thing-settings)
+    `((defun ,js--treesit-defun-type-regexp)))
+   ;; CSS thing settings
+   `(css
+     (defun ,(regexp-opt (list css--treesit-defun-type-regexp)))))
+  "Settings for `treesit-thing-settings'.")
+
+(defvar mhtml-ts-mode--treesit-indent-rules
+  (treesit--indent-rules-optimize
+   (append html-ts-mode--indent-rules
+           ;; Extended rules for js and css, to
+           ;; indent appropriately when injected
+           ;; into html
+           (treesit-modify-indent-rules
+            `((javascript ((parent-is "program")
+                           mhtml-ts-mode--js-css-tag-bol
+                           mhtml-ts-mode--js-css-indent-offset)))
+            js--treesit-indent-rules
+            :replace)
+           (treesit-modify-indent-rules
+            `((css ((parent-is "stylesheet")
+                    mhtml-ts-mode--js-css-tag-bol
+                    mhtml-ts-mode--js-css-indent-offset)))
+            css--treesit-indent-rules 'prepend)
+           :replace))
+  "Settings for `treesit-simple-indent-rules'.")
+
+(defvar mhtml-ts-mode--treesit-aggregated-simple-imenu-settings
+  `((html ,@html-ts-mode--treesit-simple-imenu-settings)
+    (javascript ,@js--treesit-simple-imenu-settings)
+    (css ,@css--treesit-simple-imenu-settings))
+  "Settings for `treesit-simple-imenu'.")
+
+;; TODO: treesit-defun-type-regexp should have an aggregated version,
+;; like treesit-aggregated-simple-imenu-settings. Otherwise we can't
+;; reuse the regex defined in the major mode we use.
+(defvar mhtml-ts-mode--treesit-defun-type-regexp
+  (regexp-opt '("class_declaration"
+                "method_definition"
+                "function_declaration"
+                "lexical_declaration"
+                "element"
+                "rule_set"))
+  "Settings for `treesit-defun-type-regexp'.")
+
+;; In order to support `prettify-symbols-mode', just `append' the prettify
+;; alist of all the languages. In our case only javascript defined this al=
ist.
+(defvar mhtml-ts-mode--prettify-symbols-alist js--prettify-symbols-alist
+  "Alist of symbol prettifications for various supported languages.")
+
+;; In order to support `which-fuction-mode' we should define
+;; a function that return the defun name.
+;; In a multilingual treesit mode, this can be implemented simply by
+;; calling language-specific functions.
+(defun mhtml-ts-mode--defun-name (node)
+  "Return the defun name of NODE.
+Return nil if there is no name or if NODE is not a defun node."
+    (let ((html-name (html-ts-mode--defun-name node))
+          (js-name (js--treesit-defun-name node))
+          (css-name (css--treesit-defun-name node)))
+      (cond
+       (html-name html-name)
+       (js-name js-name)
+       (css-name css-name))))
+
+;;; Flymake integration
+
+(defvar-local mhtml-ts-mode--flymake-process nil
+  "Store the Flymake process.")
+
+(defun mhtml-ts-mode-flymake-mhtml (report-fn &rest _args)
+  "MHTML backend for Flymake.
+Calls REPORT-FN directly.  Requires tidy."
+  (when (process-live-p mhtml-ts-mode--flymake-process)
+    (kill-process mhtml-ts-mode--flymake-process))
+  (let ((tidy (executable-find "tidy"))
+        (source (current-buffer))
+        (diagnostics-pattern (eval-when-compile
+                               (rx bol
+                                   "line " (group (+ num))    ;; :1 line
+                                   " column " (group (+ num)) ;; :2 column
+                                   " - " (group (+? nonl))    ;; :3 type
+                                   ": " (group (+? nonl))     ;; :4 msg
+                                   eol))))
+    (if (not tidy)
+        (error "Unable to find tidy command")
+      (save-restriction
+        (widen)
+        (setq mhtml-ts-mode--flymake-process
+              (make-process
+               :name "mhtml-ts-mode-flymake"
+               :noquery t
+               :connection-type 'pipe
+               :buffer (generate-new-buffer "*mhtml-ts-mode-flymake*")
+               :command `(,tidy "--gnu-emacs" "yes" "-e" "-q")
+               :sentinel
+               (lambda (proc _event)
+                 (when (eq 'exit (process-status proc))
+                   (unwind-protect
+                       (if (with-current-buffer source
+                             (eq proc mhtml-ts-mode--flymake-process))
+                           (with-current-buffer (process-buffer proc)
+                             (goto-char (point-min))
+                             (let (diags)
+                               (while (search-forward-regexp diagnostics-p=
attern nil t)
+                                 (let* ((pos
+                                         (flymake-diag-region
+                                          source
+                                          (string-to-number (match-string =
1))
+                                          (string-to-number (match-string =
2)))) ;; line and column
+                                        (type (cond ((equal (match-string =
3) "Warning") :warning)
+                                                    ((equal (match-string =
3) "Error") :error))) ;; type of message
+                                        (msg (match-string 4))) ;; message
+                                   (push (flymake-make-diagnostic source (=
car pos) (cdr pos) type msg)
+                                         diags)))
+                               (funcall report-fn diags)))
+                         (flymake-log :warning "Canceling obsolete check %=
s" proc))
+                     (kill-buffer (process-buffer proc)))))))
+        (process-send-region mhtml-ts-mode--flymake-process (point-min) (p=
oint-max))
+        (process-send-eof mhtml-ts-mode--flymake-process)))))
+
+(define-derived-mode mhtml-ts-mode html-ts-mode
+  '("HTML+" (:eval (let ((lang (mhtml-ts-mode--language-at-point (point))))
+                     (cond ((eq lang 'html) "")
+                           ((eq lang 'javascript) "JS")
+                           ((eq lang 'css) "CSS")))))
+  "Major mode for editing HTML with embedded JavaScript and CSS.
+Powered by tree-sitter."
+  (if (not (and
+            (treesit-ready-p 'html)
+            (treesit-ready-p 'javascript)
+            (treesit-ready-p 'css)))
+      (error "Tree-sitter parsers for HTML isn't available.  You can
+    install the parsers with M-x `mhtml-ts-mode-install-parsers'")
+
+    ;; When an language is embedded, you should initialize some variable
+    ;; just like it's done in the original mode.
+
+    ;; Comment.
+    ;; indenting settings for js-ts-mode.
+    (c-ts-common-comment-setup)
+    (setq-local comment-multi-line t)
+
+    ;; Font-lock.
+
+    ;; There are two ways to handle embedded code:
+    ;; 1. Use a single parser for all the embedded code in the buffer. In
+    ;; this case, the embedded code blocks are concatenated together and a=
re
+    ;; seen as a single continuous document to the parser.
+    ;; 2. Each embedded code block gets its own parser. Each parser only s=
ees
+    ;; that particular code block.
+
+    ;; If you go with 2 for a language, the local parsers are created and
+    ;; destroyed automatically by Emacs. So don't create a global parser f=
or
+    ;; that embedded language here.
+
+    ;; Create the parsers, only the global ones.
+    ;; jsdoc is a local parser, don't create a parser for it.
+    (treesit-parser-create 'css)
+    (treesit-parser-create 'javascript)
+
+    ;; Multi-language modes must set the  primary parser.
+    (setq-local treesit-primary-parser (treesit-parser-create 'html))
+
+    (setq-local treesit-range-settings
+                (treesit-range-rules
+                 :embed 'javascript
+                 :host 'html
+                 '((script_element
+                    (start_tag (tag_name))
+                    (raw_text) @cap))
+
+                 ;; Another rule could be added that when it matches an
+                 ;; attribute_value that has as its parent an
+                 ;; attribute_name "style" it captures it and then
+                 ;; passes it to the css parser.
+                 :embed 'css
+                 :host 'html
+                 '((style_element
+                    (start_tag (tag_name))
+                    (raw_text) @cap))))
+
+    ;; jsdoc is not mandatory for js-ts-mode, so we respect this by
+    ;; adding jsdoc range rules only when jsdoc is available.
+    (when (treesit-ready-p 'jsdoc t)
+      (setq-local treesit-range-settings
+                  (append treesit-range-settings
+                          (treesit-range-rules
+                           :embed 'jsdoc
+                           :host 'javascript
+                           :local t
+                           `(((comment) @cap
+                              (:match ,js--treesit-jsdoc-beginning-regexp =
@cap))))))
+      (setq-local c-ts-common--comment-regexp
+                  js--treesit-jsdoc-comment-regexp))
+
+
+    ;; Many treesit fuctions need to know the language at-point.
+    ;; So you should define such a function.
+    (setq-local treesit-language-at-point-function #'mhtml-ts-mode--langua=
ge-at-point)
+    (setq-local prettify-symbols-alist mhtml-ts-mode--prettify-symbols-ali=
st)
+
+    ;; Indent.
+
+    ;; Since `mhtml-ts-mode' inherits indentation rules from `html-ts-mode=
', `js-ts-mode'
+    ;; and `css-ts-mode', if you want to change the offset you have to act=
 on the
+    ;; *-offset variables defined for those languages.
+
+    ;; JavaScript and CSS must be indented relative to their code block.
+    ;; This is done by inserting a special rule before the normal
+    ;; indentation rules of these languages.
+    ;; The value of `mhtml-ts-mode--js-css-indent-offset' changes based on
+    ;; `mhtml-ts-mode-tag-relative-indent' and can be used to indent
+    ;; JavaScript and CSS code relative to the HTML that contains them,
+    ;; just like in mhtml-mode.
+    (setq-local treesit-simple-indent-rules mhtml-ts-mode--treesit-indent-=
rules)
+
+    ;; Navigation.
+
+    ;; This is for which-function-mode.
+    ;; Since mhtml-ts-mode is derived from html-ts-mode, which sets
+    ;; the value of `treesit-defun-type-regexp', you have to reset it to n=
il
+    ;; otherwise `imenu' and `which-function-mode' will not work.
+    (setq-local treesit-defun-type-regexp nil)
+
+    ;; This is for finding defun name, it's used by IMenu as default
+    ;; function no specific functions are defined.
+    (setq-local treesit-defun-name-function #'mhtml-ts-mode--defun-name)
+
+    ;; Define what are 'thing' for treesit.
+    ;; 'Thing' is a symbol representing the thing, like `defun', `sexp', or
+    ;; `sentence'.
+    ;; As an alternative, if you want just defun, you can define a `treesi=
t-defun-type-regexp'.
+    (setq-local treesit-thing-settings mhtml-ts-mode--treesit-thing-settin=
gs)
+
+    ;; Font-lock.
+
+    ;; In a multi-language scenario, font lock settings are usually a
+    ;; concatenation of language rules. As you can see, it is possible
+    ;; to extend/modify the default rule or use a different set of
+    ;; rules. See `php-ts-mode--custom-html-font-lock-settings' for more
+    ;; advanced usage.
+    (setq-local treesit-font-lock-settings mhtml-ts-mode--treesit-font-loc=
k-settings)
+
+    ;; Tells treesit the list of features to fontify.
+    (setq-local treesit-font-lock-feature-list mhtml-ts-mode--treesit-font=
=2Dlock-feature-list)
+
+    ;; Imenu
+
+    ;; Setup Imenu: if no function is specified, try to find an object
+    ;; using `treesit-defun-name-function'.
+    (setq-local treesit-aggregated-simple-imenu-settings
+                mhtml-ts-mode--treesit-aggregated-simple-imenu-settings)
+
+    (treesit-major-mode-setup)
+
+    ;; This is sort of a prog-mode as well as a text mode.
+    (run-mode-hooks 'prog-mode-hook)
+
+    ;; Flymake
+    (add-hook 'flymake-diagnostic-functions #'mhtml-ts-mode-flymake-mhtml =
nil 'local)))
+
+;; Add nome extra parents.
+(derived-mode-add-parents 'mhtml-ts-mode '(css-mode js-mode))
+
+(when (and (treesit-ready-p 'html) (treesit-ready-p 'javascript) (treesit-=
ready-p 'css))
+  (add-to-list
+   'auto-mode-alist '("\\.[sx]?html?\\(\\.[a-zA-Z_]+\\)?\\'" . mhtml-ts-mo=
de)))
+
+(provide 'mhtml-ts-mode)
+;;; mhtml-ts-mode.el ends here
diff --git a/lisp/treesit.el b/lisp/treesit.el
index 8d86d142e3f..00b7d266e74 100644
=2D-- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -1297,6 +1297,40 @@ treesit-font-lock-recompute-features
                        ((memq feature remove-list) nil)
                        (t current-value))))))
=20
+(defun treesit-merge-font-lock-feature-list (features-list-1 features-list=
=2D2)
+  "Merge two tree-sitter font lock feature lists.
+Returns a new font lock feature list with no duplicates in the same level.
+It can be used to merge font lock feature lists in a multi-language major =
mode.
+FEATURES-LIST-1 and FEATURES-LIST-2 are list of lists of feature symbols."
+    (let ((result nil)
+	(features-1 (car features-list-1))
+	(features-2 (car features-list-2)))
+    (while (or features-1 features-2)
+      (cond
+       ((and features-1 (not features-2)) (push features-1 result))
+       ((and (not features-1) features-2) (push features-2 result))
+       ((and features-1 features-2) (push (cl-union features-1 features-2)=
 result)))
+      (setq features-list-1 (cdr features-list-1)
+	    features-list-2 (cdr features-list-2)
+	    features-1 (car features-list-1)
+            features-2 (car features-list-2)))
+    (nreverse result)))
+
+(defun treesit-replace-font-lock-feature-settings (new-settings settings)
+  "Replaces :feature in SETTINGS with :feature from NEW-SETTINGS.
+Both SETTINGS and NEW-SETTINGS must be a value suitable for
+`treesit-font-lock-settings'.
+Return a value suitable for `treesit-font-lock-settings'"
+  (let ((result nil))
+    (dolist (new-setting new-settings)
+      (let ((new-feature (treesit-font-lock-setting-feature new-setting)))
+	(dolist (setting settings)
+	  (let ((feature (treesit-font-lock-setting-feature setting)))
+	    (if (eq new-feature feature)
+		(push new-setting result)
+	      (push setting result))))))
+    (nreverse result)))
+
 (defun treesit-add-font-lock-rules (rules &optional how feature)
   "Add font-lock RULES to the current buffer.
=20
@@ -2401,6 +2435,37 @@ treesit--indent-rules-optimize
                             offset)))))
              (cons lang (mapcar #'optimize-rule indent-rules)))))
=20
+(defun treesit-modify-indent-rules (new-rules rules &optional how)
+  "Modify RULES using NEW-RULES.
+As default replace rules with the same anchor.
+When HOW is :prepend NEW-RULES are prepend to RULES, when
+:append NEW-RULES are appended to RULES, when :replace (the default)
+NEW-RULES replace rule in RULES which the same anchor."
+  (let ((n-lang (car (car new-rules)))
+	(lang (car (car rules))))
+    (when (not (eq n-lang lang))
+      (error "The language must be the same"))
+    (let* ((nr (cdr (car new-rules)))
+           (r (cdr (car rules)))
+           (tmp nil)
+           (result
+            (cond
+             ((eq how :prepend)
+	      (append nr r))
+             ((eq how :append)
+              (append r nr))
+             ((or (eq how :replace) t)
+              (nreverse
+               (progn
+                 (dolist (new-rule nr)
+	           (dolist (rule r)
+	             (if (equal (nth 0 new-rule) (nth 0 rule))
+		         (push new-rule tmp)
+		       (push rule tmp))))
+                 tmp))))))
+      (push lang result)
+      `(,result))))
+
 ;;; Search
=20
 (defun treesit-search-forward-goto
=2D-=20
2.48.1


--nextPart2311485.vFx2qVVIhK--







Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 18 Jan 2025 01:38:15 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Fri Jan 17 20:38:15 2025
Received: from localhost ([127.0.0.1]:39435 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tYxmx-000401-0F
	for submit <at> debbugs.gnu.org; Fri, 17 Jan 2025 20:38:15 -0500
Received: from mail-pl1-x62b.google.com ([2607:f8b0:4864:20::62b]:59469)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <casouri@HIDDEN>) id 1tYxmu-0003zj-4E
 for 74610 <at> debbugs.gnu.org; Fri, 17 Jan 2025 20:38:13 -0500
Received: by mail-pl1-x62b.google.com with SMTP id
 d9443c01a7336-21675fd60feso60991575ad.2
 for <74610 <at> debbugs.gnu.org>; Fri, 17 Jan 2025 17:38:12 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1737164286; x=1737769086; darn=debbugs.gnu.org;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:from:to:cc:subject:date
 :message-id:reply-to;
 bh=glR9L1YZCnrYKJzMaKx/3XtlZr/4mND/v/YtzC8WjNw=;
 b=Q2M5EOMUuX13pX4m1kXP1qV9YPGjF6avTCfwO++W13L5R0ZhiwJizbvA6d+TGRhjvC
 7cL2scYbSGTmCxiRkg9dQCmqAWOnNeVzIBiaPB89zY9I17WSYy8v2NfN0U4ZcMHPhGxn
 p6YViQJQgHpaRu23cEFP35+RGZB3MhsykH85se0vbXptAjXqqfDO9SborL8EFxfejbwe
 eRXe2Z2GQRF2toGs5+hcT/Hzqg+n5/gQlLb1RgzFBpzVOPTxuQXC6Iw351Bd1YwMb4tI
 Li6f2HnUcrCocZUMBcQf1HMaUEC1tRbw9FMTloyJ3kCyAFPXB/QXSfJ1kYshAcrNwauY
 plfQ==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1737164286; x=1737769086;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=glR9L1YZCnrYKJzMaKx/3XtlZr/4mND/v/YtzC8WjNw=;
 b=OIOds7e0JphO+qA+1/ZaXo8vDLSB5aK7CGi7KcwbVmVB2ctrlCus2+flkzabHWhxLk
 wmZeciuF1pEjxLAWUEKmO+TGmcPVKndYwqBb7hfziFSX8gRS82ZnexRtd+g3mefATPxo
 3O0t9Y6p5owqGUb50R2Glxh6rVaAatmALClOZTHRevrB50iZQIneva7HeURQlimdsr0e
 AYP7YqLxKq9w4WjlW5a1FswI3JKP2RjCaf5Vy2F13N68dhRtHsnDZIvuG6NmxbEwSuqf
 8AJ224vkSsz3Wv9QZ4qWkeqA/MYAnGAps+x4aXp0fTPVYiUMAZYZfAvkqXgwUg2SZv26
 HMXg==
X-Forwarded-Encrypted: i=1;
 AJvYcCVLXMUFlN6gc/VjA7YsBSNIVMGCkVSUP/jVEmPIQ7tnMiEXTck2tjIitUYHC5HDXCvt+/oGBQ==@debbugs.gnu.org
X-Gm-Message-State: AOJu0YyO9ymWzzF4j2IGXUCoB1Rsml0qV25QJ33B0yd8igDNybsOaGRY
 sDLRM67tpSZDn0AxzoWL4y7XEzg8awikmXmJWKBWZkXKU5Djb1gZ
X-Gm-Gg: ASbGncurOmnf31dglRlwV+YW9bIhrK4YkcFv76E/QGLrEHKVF8UA5yvk3ubGqHUWRFv
 q5rXMz+lKtl5JR+VROfv73j7wYO17fkKZ/A4/T7+IyfI1jNFBf8lWNZLEiAFJ7kdzgLLzB5I0S4
 sK7uHYtzh16lUn+1xrwTj76VumB3V3mk7rl//is0Gcdlj2/o54HFWprTElMe3dlBb5ztaKSgHjF
 m6HNXyWeN+P/Zyo1ijx+4exqQHIwEjKbINKTYOirkayrTfqo0ZRU/Ezw+si2jgDalllsrkoueef
 qMvRELfY0FLueylBfQ==
X-Google-Smtp-Source: AGHT+IF9g1rBKuzQ81hGEhhGn0aR2xlLP6HNR83UjxPaXwkqAR7R19KJW26iU0MNt/D4ATWQPS3OuQ==
X-Received: by 2002:a05:6a20:12d6:b0:1e5:dace:693f with SMTP id
 adf61e73a8af0-1eb2144d3f1mr7841905637.4.1737164285837; 
 Fri, 17 Jan 2025 17:38:05 -0800 (PST)
Received: from smtpclient.apple ([2601:646:8f81:6120:290c:bb92:38fe:ad4d])
 by smtp.gmail.com with ESMTPSA id
 d2e1a72fcca58-72daba4895csm2679177b3a.130.2025.01.17.17.38.04
 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);
 Fri, 17 Jan 2025 17:38:05 -0800 (PST)
Content-Type: text/plain;
	charset=utf-8
Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3776.700.51\))
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
From: Yuan Fu <casouri@HIDDEN>
In-Reply-To: <2462456.cojqenx9y0@fedora>
Date: Fri, 17 Jan 2025 17:37:53 -0800
Content-Transfer-Encoding: quoted-printable
Message-Id: <2ABDEC5D-B17E-4DD3-9371-48B61026A144@HIDDEN>
References: <3532547.LZWGnKmheA@fedora>
 <018B485B-8F4D-4472-BD21-384C7EC9E31C@HIDDEN>
 <D06621B4-736C-4729-A8E4-3D5885F498F4@HIDDEN> <2462456.cojqenx9y0@fedora>
To: Vincenzo Pupillo <v.pupillo@HIDDEN>
X-Mailer: Apple Mail (2.3776.700.51)
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org,
 Juri Linkov <juri@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)



> On Jan 14, 2025, at 1:41=E2=80=AFPM, Vincenzo Pupillo =
<v.pupillo@HIDDEN> wrote:
>=20
> Ciao Yuan and Juri,=20
> this is an updated version of mhtml-ts-mode.=20
> I have tried to reduce as much as possible copies of parts of the =
major modes=20
> from which it is derived.
> To do this, I had to move some values that were assigned directly to =
treesit's=20
> own variables  (in ccs-mode.el, in js.el, and in html-ts-mode.el) into =
new=20
> variables.=20
> I also added three new functions to treesit.el to make it easier to =
combine=20
> parts derived from the other major modes. So now any changes to these =
new=20
> variables are directly reflected in the behavior of mhtml-ts-mode.
> There are a few things I would like to highlight:
> 1. treesit-font-lock-feature-lists are not defined per parser, so =
simply=20
> merging the different lists will cause display differences from the =
original=20
> major-modes; for example =E2=80=9Cfunction=E2=80=9D is defined at =
level 3 in css-ts-mode but=20
> at level 4 in js-ts-mode.

Yeah, that=E2=80=99s not pretty, I don=E2=80=99t have a good solution =
for now. Technically a major mode can use =
treesit-font-lock-recompute-features after treesit-major-mode-setup to =
do fine adjustments, but it=E2=80=99s ugly.

> 2. treesit-defun-type-regexp has the same problem as =
treesit-font-lock-
> feature-list, so I had to define it myself.

Maybe you can use treesit-thing-settings instead?

>=20
> But other than that, it works pretty well.
>=20
> IMHO, a global "list" where you can define "font-lock", "indent-list", =
"font-
> lock-feature", etc. by language (perhaps with getter and setter =
methods) might=20
> make it easier to define new multilingual major-modes. It could =
improve the=20
> decoupling between multilingual major-modes and the major-modes they =
are=20
> derived from. It could also better decouple the internal =
implementation of=20
> treesit.el from the treesitter-based major-modes.
>=20
> Let me know what you think.

That=E2=80=99s what I had in mind. That=E2=80=99s also why I added a new =
variable treesit-aggregated-simple-imenu-settings instead of =
piggybacking on treesit-simple-imenu-settings. A separate variable is =
simpler to borrow settings from.

But I didn=E2=80=99t have time to think it though yet. (By language, or =
by mode and language? Should packages set it on load, or define a =
function that loads these settings? Etc.)=20

Yuan=




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 15 Jan 2025 08:59:13 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Jan 15 03:59:13 2025
Received: from localhost ([127.0.0.1]:56929 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tXzF3-0001H0-E3
	for submit <at> debbugs.gnu.org; Wed, 15 Jan 2025 03:59:13 -0500
Received: from mail-wm1-x32e.google.com ([2a00:1450:4864:20::32e]:59456)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <v.pupillo@HIDDEN>)
 id 1tXzEz-0001Gd-GZ
 for 74610 <at> debbugs.gnu.org; Wed, 15 Jan 2025 03:59:10 -0500
Received: by mail-wm1-x32e.google.com with SMTP id
 5b1f17b1804b1-436202dd730so45678435e9.2
 for <74610 <at> debbugs.gnu.org>; Wed, 15 Jan 2025 00:59:09 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1736931543; x=1737536343; darn=debbugs.gnu.org;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:from:to:cc:subject:date
 :message-id:reply-to;
 bh=YJDbeavibiRamO5hd64k7Ntf04SYcK6Qm0pw2VMaPao=;
 b=mlAnyxRCsC/kLmfO3aJ9SE47hZy/lTH8P4MDMzgf0JeJRJfKJIPmJekYmk9H0m+e52
 NAZEOXPgvfNZVElUCDn2migmnGPKUWmGILK8dRILQKGIIPrs6siRNieZBd0eLUbMwNsx
 D3hqBLnqUYvkz6iqgf7BmaZSoBCGaZaExHb5J0MnVirB7t1Q3MXPHG+KdhCPgf0oTsRO
 zD6OWEJDhHzPEQ4owD36GAS2W4ThkIawYebdziEsExpIKK4uqC7jFCwFvPjfGK4x0Jh/
 GV6FBmyjqwcJn49A2v8RLFckiTg3z1IBjy9E9v3to420dTNGU/QfZN947aKvKoxQCf9U
 Gn7g==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1736931543; x=1737536343;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=YJDbeavibiRamO5hd64k7Ntf04SYcK6Qm0pw2VMaPao=;
 b=bt5SVsrQ7qZeisIc/q+XDfbwGi+Li5trYFYYCU3KzNfh1KI1wkE6EOxhrneieXQz+k
 NH41Y4Ara0DNN6MrUFgDKq7sTa8nnKVcxaKF8nvlpJ/0cWjUO8QwAmN0dWCjl0xy+uqZ
 eOYw4aFRXtM4ykersUR7PWaf4QYbPdthmHCrAl9evA/mQ/a6EQZNnm7nKl+QC3aiyoB7
 Lnp6Y23FX11ILn/FcITAL2UE0M1jCtV5c1aReyqJgCyuJgDSsuHClSgNYZfz7PcPoqgU
 OljjjhrPnuneZ85iGzXISBhRt/t5ALJy7N7jmhDyqtNsqBzPvP4D0NWBcM3kn3YmvRt3
 VEdQ==
X-Forwarded-Encrypted: i=1;
 AJvYcCW+yf3H7usDb+UEpnXY7z6KMjmJ8qjgjEPiC46NW0E1bUhl03e9LHZkL3iDFATlYnhT4uiMzg==@debbugs.gnu.org
X-Gm-Message-State: AOJu0YwQjHxLHHaVzqfGewvhfp/mbHqGXhZIdot3EU91rQIVyzC9LN2Z
 brL/Z1nx4R7UOiqI+hYzq89sCDzmCyiZBKVG+wacvlx0oVjdZDT8
X-Gm-Gg: ASbGncvwk8w4AeSI8R58PNkS4foKFpHYhg+mg1nBo+mB4gvBnEF9aQQgsndxEbaXXdz
 hvHSCGIZwOUD/Ly0Z+PqlIxMkODPhELnsZYMFe82wdIZhZM1EItY6ZPAyUc8AByynxcx/RzvCo5
 fXMVBuKICvp8vYO2nBOTwqo2prOvxDmmBnO7HrHghUvpCTGP6QjE5b5eu9zIpqoFYFhhd/WpFjc
 d8rSdobL3rX/kpksLLPEfCxdOVqtlDVR2zKli4DSFOkb87k4lNorOao8Srm06BHCXkxwKESu1te
 AXCDB7ZwSg==
X-Google-Smtp-Source: AGHT+IGFiGukSi7Bf+hHR5S1gXIrvn2fM+U33F+uHCnHxvmj5tV0j9JBtE/odJfgNqGkaw8CrQQhSA==
X-Received: by 2002:a05:600c:4e92:b0:434:fdbc:5ce5 with SMTP id
 5b1f17b1804b1-436e2707f4amr259943125e9.29.1736931542108; 
 Wed, 15 Jan 2025 00:59:02 -0800 (PST)
Received: from 3-191.divsi.unimi.it (3-191.divsi.unimi.it. [159.149.3.191])
 by smtp.gmail.com with ESMTPSA id
 ffacd0b85a97d-38a8e4c1ce5sm17292527f8f.94.2025.01.15.00.59.00
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Wed, 15 Jan 2025 00:59:01 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: Juri Linkov <juri@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Wed, 15 Jan 2025 09:59:00 +0100
Message-ID: <2762502.lGaqSPkdTl@HIDDEN>
In-Reply-To: <87cygofrdd.fsf@HIDDEN>
References: <3532547.LZWGnKmheA@fedora> <2462456.cojqenx9y0@fedora>
 <87cygofrdd.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="nextPart2576831.XAFRqVoOGU"
Content-Transfer-Encoding: 7Bit
X-Debbugs-Envelope-To: 74610
Cc: Yuan Fu <casouri@HIDDEN>, Eli Zaretskii <eliz@HIDDEN>,
 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

This is a multi-part message in MIME format.

--nextPart2576831.XAFRqVoOGU
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"

Ciao Juri,

In data mercoled=C3=AC 15 gennaio 2025 08:50:06 Ora standard dell=E2=80=99E=
uropa centrale,=20
Juri Linkov ha scritto:
> > this is an updated version of mhtml-ts-mode.
>=20
> Thanks.  Please also attach a new version of the mhtml-ts-mode.el file.
Okay, I have attached all the files that I have modified.

>=20
> > I have tried to reduce as much as possible copies of parts of the major
> > modes from which it is derived.
> >=20
> > To do this, I had to move some values that were assigned directly to
> > treesit's own variables  (in ccs-mode.el, in js.el, and in
> > html-ts-mode.el) into new variables.
>=20
> I had a thought that maybe for treesit-thing-settings we could have
> a global registry like auto-mode-alist, not a buffer-local value.
> Then modes could just add own info to it. e.g.
>=20
> (add-to-list 'treesit-thing-global-settings
>              '(javascript (sentence ...)))
>=20
> This can be shared between embedded ts-modes.
Yes, that is exactly what I was thinking, but I obviously explained it wron=
g.

Thanks.

Vincenzo



--nextPart2576831.XAFRqVoOGU
Content-Disposition: attachment; filename="treesit.el"
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-emacs-lisp; charset="UTF-8"; name="treesit.el"

;;; treesit.el --- tree-sitter utilities -*- lexical-binding: t -*-

;; Copyright (C) 2021-2025 Free Software Foundation, Inc.

;; Maintainer: =E4=BB=98=E7=A6=B9=E5=AE=89 (Yuan Fu) <casouri@HIDDEN>
;; Keywords: treesit, tree-sitter, languages
;; Package: emacs

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; This file is the Lisp counterpart of treesit.c.  Together they
;; provide tree-sitter integration for Emacs.  This file contains
;; convenient functions that are more idiomatic and flexible than the
;; exposed C API of tree-sitter.  It also contains frameworks for
;; integrating tree-sitter with font-lock, indentation, activating and
;; deactivating tree-sitter, debugging tree-sitter, etc.

;;; Code:

(eval-when-compile (require 'subr-x)) ; For `string-join'.
(require 'cl-lib)
(require 'font-lock)
(require 'seq)

;;; Function declarations

(defmacro treesit-declare-unavailable-functions ()
  "Declare C functions and variables defined in treesit.c.

This macro is only needed when a file needs to be able to byte-compile
in a Emacs not built with tree-sitter library."
  '(progn
     (declare-function treesit-language-available-p "treesit.c")
     (declare-function treesit-language-version "treesit.c")

     (declare-function treesit-parser-p "treesit.c")
     (declare-function treesit-node-p "treesit.c")
     (declare-function treesit-compiled-query-p "treesit.c")
     (declare-function treesit-query-p "treesit.c")
     (declare-function treesit-query-language "treesit.c")

     (declare-function treesit-node-parser "treesit.c")

     (declare-function treesit-parser-create "treesit.c")
     (declare-function treesit-parser-delete "treesit.c")
     (declare-function treesit-parser-list "treesit.c")
     (declare-function treesit-parser-buffer "treesit.c")
     (declare-function treesit-parser-language "treesit.c")
     (declare-function treesit-parser-tag "treesit.c")

     (declare-function treesit-parser-root-node "treesit.c")

     (declare-function treesit-parser-set-included-ranges "treesit.c")
     (declare-function treesit-parser-included-ranges "treesit.c")
     (declare-function treesit-parser-changed-ranges "treesit.c")
     (declare-function treesit-parser-add-notifier "treesit.c")

     (declare-function treesit-node-type "treesit.c")
     (declare-function treesit-node-start "treesit.c")
     (declare-function treesit-node-end "treesit.c")
     (declare-function treesit-node-string "treesit.c")
     (declare-function treesit-node-parent "treesit.c")
     (declare-function treesit-node-child "treesit.c")
     (declare-function treesit-node-check "treesit.c")
     (declare-function treesit-node-field-name-for-child "treesit.c")
     (declare-function treesit-node-child-count "treesit.c")
     (declare-function treesit-node-child-by-field-name "treesit.c")
     (declare-function treesit-node-next-sibling "treesit.c")
     (declare-function treesit-node-prev-sibling "treesit.c")
     (declare-function treesit-node-first-child-for-pos "treesit.c")
     (declare-function treesit-node-descendant-for-range "treesit.c")
     (declare-function treesit-node-eq "treesit.c")

     (declare-function treesit-pattern-expand "treesit.c")
     (declare-function treesit-query-expand "treesit.c")
     (declare-function treesit-query-compile "treesit.c")
     (declare-function treesit-query-capture "treesit.c")

     (declare-function treesit-search-subtree "treesit.c")
     (declare-function treesit-search-forward "treesit.c")
     (declare-function treesit-induce-sparse-tree "treesit.c")
     (declare-function treesit-subtree-stat "treesit.c")
     (declare-function treesit-node-match-p "treesit.c")

     (declare-function treesit-available-p "treesit.c")

     (defvar treesit-thing-settings)))

(treesit-declare-unavailable-functions)

;;; Custom options

;; Tree-sitter always appear as treesit in symbols.
(defgroup treesit nil
  "Incremental parser.
It is used to enhance major mode features like font-lock,
indent, imenu, etc."
  :group 'tools
  :version "29.1")

(defcustom treesit-max-buffer-size
  (let ((mb (* 1024 1024)))
    ;; 40MB for 64-bit systems, 15 for 32-bit.
    (if (or (< most-positive-fixnum (* 2.0 1024 mb))
            ;; 32-bit system with wide ints.
            (string-search "--with-wide-int" system-configuration-options))
        (* 15 mb)
      (* 40 mb)))
  "Maximum buffer size (in bytes) for enabling tree-sitter parsing.

A typical tree-sitter parser needs 10 times as much memory as the
buffer it parses.  Also, the tree-sitter library has a hard limit
of max unsigned 32-bit value for byte offsets into buffer text."
  :type 'integer
  :version "29.1")

;;; Parser API supplement

(defvar-local treesit-language-at-point-function nil
  "A function that returns the language at point.
This is used by `treesit-language-at', which is used by various
functions to determine which parser to use at point.

The function is called with one argument, the position of point.

In general, this function should call `treesit-node-at' with an
explicit language (usually the host language), and determine the
language at point using the type of the returned node.

DO NOT derive the language at point from parser ranges.  It's
cumbersome and can't deal with some edge cases.")

(defun treesit-language-at (position)
  "Return the language at POSITION.

This function assumes that parser ranges are up-to-date.  It
returns the return value of `treesit-language-at-point-function'
if it's non-nil, otherwise it returns the language of the first
parser in `treesit-parser-list', or nil if there is no parser.

In a multi-language buffer, make sure
`treesit-language-at-point-function' is implemented!  Otherwise
`treesit-language-at' wouldn't return the correct result."
  (if treesit-language-at-point-function
      (funcall treesit-language-at-point-function position)
    (when-let* ((parser (car (treesit-parser-list))))
      (treesit-parser-language parser))))

;;; Node API supplement

(define-error 'treesit-no-parser "No available parser for this buffer"
              'treesit-error)

(defun treesit-node-buffer (node)
  "Return the buffer in which NODE belongs."
  (treesit-parser-buffer
   (treesit-node-parser node)))

(defun treesit-node-language (node)
  "Return the language symbol that NODE's parser uses."
  (treesit-parser-language
   (treesit-node-parser node)))

(defun treesit-node-at (pos &optional parser-or-lang named)
  "Return the leaf node at position POS.

A leaf node is a node that doesn't have any child nodes.

The returned node's span covers POS: the node's beginning is before
or at POS, and the node's end is after POS.

If no such node exists, but there's a leaf node which ends at POS,
return that node.

Otherwise (e.g., when POS is on whitespace between two leaf
nodes), return the first leaf node after POS.

If there is no leaf node after POS, return the first leaf node
before POS.

Return nil if no leaf node can be returned.  If NAMED is non-nil,
only look for named nodes.

If PARSER-OR-LANG is a parser, use that parser; if PARSER-OR-LANG
is a language, find the first parser for that language in the
current buffer, or create one if none exists; If PARSER-OR-LANG
is nil, try to guess the language at POS using `treesit-language-at'.

If there's a local parser at POS, the local parser takes priority
unless PARSER-OR-LANG is a parser, or PARSER-OR-LANG is a
language and doesn't match the language of the local parser."
  (let* ((root (if (treesit-parser-p parser-or-lang)
                   (treesit-parser-root-node parser-or-lang)
                 (or (when-let* ((parser
                                  (car (treesit-local-parsers-at
                                        pos parser-or-lang))))
                       (treesit-parser-root-node parser))
                     (treesit-buffer-root-node
                      (or parser-or-lang
                          (treesit-language-at pos))))))
         (node root)
         (node-before root)
         (pos-1 (max (1- pos) (point-min)))
         next)
    (when node
      ;; This is very fast so no need for C implementation.
      (while (setq next (treesit-node-first-child-for-pos
                         node pos named))
        (setq node next))
      ;; If POS is at the end of buffer, after all the text, we will
      ;; end up with NODE =3D root node.  Instead of returning nil,
      ;; return the last leaf node in the tree for convenience.
      (if (treesit-node-eq node root)
          (progn
            (while (setq next (treesit-node-child node -1 named))
              (setq node next))
            node)
        ;; Normal case, where we found a node.
        (if (<=3D (treesit-node-start node) pos)
            node
          ;; So the node we found is completely after POS, try to find
          ;; a node whose end equals to POS.
          (while (setq next (treesit-node-first-child-for-pos
                             node-before pos-1 named))
            (setq node-before next))
          (if (eq (treesit-node-end node-before) pos)
              node-before
            node))))))

(defun treesit-node-on (beg end &optional parser-or-lang named)
  "Return the smallest node covering BEG to END.

BEWARE!  Calling this function on an empty line that is not
inside any top-level construct (function definition, etc.) most
probably will give you the root node, because the root node is
the smallest node that covers that empty line.  You probably want
to use `treesit-node-at' instead.

Return nil if none was found.  If NAMED is non-nil, only look for
named node.

If PARSER-OR-LANG is a parser, use that parser; if PARSER-OR-LANG
is a language, find the first parser for that language in the
current buffer, or create one if none exists; If PARSER-OR-LANG
is nil, try to guess the language at BEG using `treesit-language-at'.

If there's a local parser between BEG and END, try to use that
parser first."
  (let* ((lang-at-point (treesit-language-at beg))
         (root (if (treesit-parser-p parser-or-lang)
                   (treesit-parser-root-node parser-or-lang)
                 (or (when-let* ((parser
                                  (car (treesit-local-parsers-on
                                        beg end (or parser-or-lang
                                                    lang-at-point)))))
                       (treesit-parser-root-node parser))
                     (treesit-buffer-root-node
                      (or parser-or-lang lang-at-point))))))
    (treesit-node-descendant-for-range root beg (or end beg) named)))

(defun treesit-node-top-level (node &optional pred include-node)
  "Return the top-level equivalent of NODE.

Specifically, return the highest parent of NODE that has the same
type as it.  If no such parent exists, return nil.

If PRED is non-nil, match each parent's type with PRED rather
than using NODE's type.  PRED can also be a predicate function,
and more.  See `treesit-thing-settings' for details.

If INCLUDE-NODE is non-nil, return NODE if it satisfies PRED."
  (let ((pred (or pred (rx bos (literal (treesit-node-type node)) eos)))
        (result nil))
    (cl-loop for cursor =3D (if include-node node
                            (treesit-node-parent node))
             then (treesit-node-parent cursor)
             while cursor
             if (treesit-node-match-p cursor pred t)
             do (setq result cursor))
    result))

(defun treesit-buffer-root-node (&optional language tag)
  "Return the root node of the current buffer.

Use the first parser in the parser list if LANGUAGE is omitted.

If LANGUAGE is non-nil, use the first parser for LANGUAGE with
TAG in the parser list, or create one if none exists.  TAG
defaults to nil."
  (if-let* ((parser
             (if language
                 (treesit-parser-create language nil nil tag)
               (or (car (treesit-parser-list))
                   (signal 'treesit-no-parser (list (current-buffer)))))))
      (treesit-parser-root-node parser)))

(defun treesit-filter-child (node pred &optional named)
  "Return children of NODE that satisfies predicate PRED.
PRED is a function that takes one argument, the child node.
If optional argument NAMED is non-nil, only search for named
node."
  (let ((child (treesit-node-child node 0 named))
        result)
    (while child
      (when (funcall pred child)
        (push child result))
      (setq child (treesit-node-next-sibling child named)))
    (reverse result)))

(defun treesit-node-text (node &optional no-property)
  "Return the buffer (or string) content corresponding to NODE.
If optional argument NO-PROPERTY is non-nil, remove text
properties."
  (when node
    (with-current-buffer (treesit-node-buffer node)
      (if no-property
          (buffer-substring-no-properties
           (treesit-node-start node)
           (treesit-node-end node))
        (buffer-substring
         (treesit-node-start node)
         (treesit-node-end node))))))

(defun treesit-parent-until (node pred &optional include-node)
  "Return the closest parent of NODE that satisfies PRED.

This function successively examines the parent of NODE, then
the parent of the parent, etc., until it finds the first
ancestor node which satisfies the predicate PRED; then it
returns that ancestor node.  It returns nil if no ancestor
node was found that satisfies PRED.

PRED can be a predicate function, a regexp matching node type,
and more; see docstring of `treesit-thing-settings'.

If INCLUDE-NODE is non-nil, return NODE if it satisfies PRED."
  (let ((node (if include-node node
                (treesit-node-parent node))))
    (while (and node (not (treesit-node-match-p node pred)))
      (setq node (treesit-node-parent node)))
    node))

(defun treesit-parent-while (node pred)
  "Return the furthest parent of NODE (including NODE) that satisfies PRED.

This function successively examines NODE, the parent of NODE,
then the parent of the parent, etc., until it finds a node which
no longer satisfies the predicate PRED; it returns the last
examined node that satisfies PRED.  If no node satisfies PRED, it
returns nil.

PRED can be a predicate function, a regexp matching node type,
and more; see docstring of `treesit-thing-settings'."
  (let ((last nil))
    (while (and node (treesit-node-match-p node pred))
      (setq last node
            node (treesit-node-parent node)))
    last))

(defun treesit-node-children (node &optional named)
  "Return a list of NODE's children.
If NAMED is non-nil, collect named child only."
  (mapcar (lambda (idx)
            (treesit-node-child node idx named))
          (number-sequence
           0 (1- (treesit-node-child-count node named)))))

(defun treesit-node-index (node &optional named)
  "Return the index of NODE in its parent.
If NAMED is non-nil, count named child only."
  (let ((count 0))
    (while (setq node (treesit-node-prev-sibling node named))
      (cl-incf count))
    count))

(defun treesit-node-field-name (node)
  "Return the field name of NODE as a child of its parent."
  (when-let* ((parent (treesit-node-parent node))
              (idx (treesit-node-index node)))
    (treesit-node-field-name-for-child parent idx)))

(defun treesit-node-get (node instructions)
  "Get things from NODE by INSTRUCTIONS.

This is a convenience function that chains together multiple node
accessor functions together.  For example, to get NODE's parent's
next sibling's second child's text, call

   (treesit-node-get node
     \\=3D'((parent 1)
       (sibling 1 nil)
       (child 1 nil)
       (text nil)))

INSTRUCTION is a list of INSTRUCTIONs of the form (FN ARG...).
The following FN's are supported:

\(child IDX NAMED)    Get the IDX'th child
\(parent N)           Go to parent N times
\(field-name)         Get the field name of the current node
\(type)               Get the type of the current node
\(text NO-PROPERTY)   Get the text of the current node
\(children NAMED)     Get a list of children
\(sibling STEP NAMED) Get the nth prev/next sibling, negative STEP
                     means prev sibling, positive means next

Note that arguments like NAMED and NO-PROPERTY can't be omitted,
unlike in their original functions."
  (declare (indent 1))
  (while (and node instructions)
    (pcase (pop instructions)
      ('(field-name) (setq node (treesit-node-field-name node)))
      ('(type) (setq node (treesit-node-type node)))
      (`(child ,idx ,named) (setq node (treesit-node-child node idx named)))
      (`(parent ,n) (dotimes (_ n)
                      (setq node (treesit-node-parent node))))
      (`(text ,no-property) (setq node (treesit-node-text node no-property)=
))
      (`(children ,named) (setq node (treesit-node-children node named)))
      (`(sibling ,step ,named)
       (dotimes (_ (abs step))
         (setq node (if (> step 0)
                        (treesit-node-next-sibling node named)
                      (treesit-node-prev-sibling node named)))))))
  node)

(defun treesit-node-enclosed-p (smaller larger &optional strict)
  "Return non-nil if SMALLER is enclosed in LARGER.
SMALLER and LARGER can be either (BEG . END) or a node.

Return non-nil if LARGER's start <=3D SMALLER's start and LARGER's
end <=3D SMALLER's end.

If STRICT is t, compare with < rather than <=3D.

If STRICT is \\=3D'partial, consider LARGER encloses SMALLER when
at least one side is strictly enclosing."
  (unless (and (or (consp larger) (treesit-node-p larger))
               (or (consp smaller) (treesit-node-p smaller)))
    (signal 'wrong-type-argument '((or cons treesit-node))))
  (let ((larger-start (if (consp larger)
                          (car larger)
                        (treesit-node-start larger)))
        (larger-end (if (consp larger)
                        (cdr larger)
                      (treesit-node-end larger)))
        (smaller-start (if (consp smaller)
                           (car smaller)
                         (treesit-node-start smaller)))
        (smaller-end (if (consp smaller)
                         (cdr smaller)
                       (treesit-node-end smaller))))
    (pcase strict
      ('t (and (< larger-start smaller-start)
               (< smaller-end larger-end)))
      ('partial (and (or (not (eq larger-start smaller-start))
                         (not (eq larger-end smaller-end)))
                     (<=3D larger-start smaller-start
                         smaller-end larger-end)))
      (_ (<=3D larger-start smaller-start smaller-end larger-end)))))

;;; Query API supplement

(defun treesit-query-string (string query language)
  "Query STRING with QUERY in LANGUAGE.
See `treesit-query-capture' for QUERY."
  (with-temp-buffer
    (insert string)
    (let ((parser (treesit-parser-create language)))
      (treesit-query-capture
       (treesit-parser-root-node parser)
       query))))

(defun treesit-query-range (node query &optional beg end offset)
  "Query the current buffer and return ranges of captured nodes.

QUERY, NODE, BEG, END are the same as in `treesit-query-capture'.
This function returns a list of (START . END), where START and
END specifics the range of each captured node.  OFFSET is an
optional pair of numbers (START-OFFSET . END-OFFSET).  The
respective offset values are added to each (START . END) range
being returned.  Capture names generally don't matter, but names
that starts with an underscore are ignored."
  (let ((offset-left (or (car offset) 0))
        (offset-right (or (cdr offset) 0)))
    (cl-loop for capture
             in (treesit-query-capture node query beg end)
             for name =3D (car capture)
             for node =3D (cdr capture)
             if (not (string-prefix-p "_" (symbol-name name)))
             collect (cons (+ (treesit-node-start node) offset-left)
                           (+ (treesit-node-end node) offset-right)))))

;;; Range API supplement

(defvar-local treesit-range-settings nil
  "A list of range settings.

Each element of the list is of the form (QUERY LANGUAGE LOCAL-P
OFFSET).  When updating the range of each parser in the buffer,
`treesit-update-ranges' queries each QUERY, and sets LANGUAGE's
range to the range spanned by captured nodes.  QUERY must be a
compiled query.  If LOCAL-P is t, give each range a separate
local parser rather than using a single parser for all the
ranges.  If OFFSET is non-nil, it should be a cons of
numbers (START-OFFSET . END-OFFSET), where the start and end
offset are added to each queried range to get the result ranges.

Capture names generally don't matter, but names that starts with
an underscore are ignored.

QUERY can also be a function, in which case it is called with 2
arguments, START and END.  It should ensure parsers' ranges are
correct in the region between START and END.

The exact form of each setting is considered internal and subject
to change.  Use `treesit-range-rules' to set this variable.")

(defun treesit-range-rules (&rest query-specs)
  "Produce settings for `treesit-range-settings'.

QUERY-SPECS are a series of QUERY-SPECs, where each QUERY-SPEC is
a QUERY preceded by zero or more pairs of :KEYWORD and VALUE,
like this:

    :KEYWORD VALUE... QUERY

Each QUERY is a tree-sitter query in either the string,
s-expression or compiled form.

Capture names generally don't matter, but names that starts with
an underscore are ignored.

=46or each QUERY, :KEYWORD and VALUE pairs add meta information to
it.  For example,

    (treesit-range-rules
     :embed \\=3D'javascript
     :host \\=3D'html
     :offset \\=3D'(1 . -1)
     \\=3D'((script_element (raw_text) @cap)))

The `:embed' keyword specifies the embedded language, and the
`:host' keyword specifies the host language.  They are used in
this way: Emacs queries QUERY in the host language's parser,
computes the ranges spanned by the captured nodes, and applies
these ranges to parsers for the embedded language.

If there's a `:local' keyword with value t, the range computed by
this QUERY is given a dedicated local parser.  Otherwise, the
range shares the same parser with other ranges.

If there's an `:offset' keyword with a pair of numbers, each
captured range is offset by those numbers.  For example, an
offset of (1 . -1) will update a captured range of (2 . 8) to
be (3 . 7).  This can be used to exclude things like surrounding
delimiters from being included in the range covered by an
embedded parser.

QUERY can also be a function that takes two arguments, START and
END.  If QUERY is a function, it doesn't need the :KEYWORD VALUE
pair preceding it.  This function should set the ranges for
parsers in the current buffer in the region between START and
END.  It is OK for this function to set ranges in a larger region
that encompasses the region between START and END."
  (let (host embed offset result local)
    (while query-specs
      (pcase (pop query-specs)
        (:local (when (eq t (pop query-specs))
                  (setq local t)))
        (:host (let ((host-lang (pop query-specs)))
                 (unless (symbolp host-lang)
                   (signal 'treesit-error (list "Value of :host option shou=
ld be a symbol" host-lang)))
                 (setq host host-lang)))
        (:embed (let ((embed-lang (pop query-specs)))
                  (unless (symbolp embed-lang)
                    (signal 'treesit-error (list "Value of :embed option sh=
ould be a symbol" embed-lang)))
                  (setq embed embed-lang)))
        (:offset (let ((range-offset (pop query-specs)))
                   (unless (and (consp range-offset)
                                (numberp (car range-offset))
                                (numberp (cdr range-offset)))
                     (signal 'treesit-error (list "Value of :offset option =
should be a pair of numbers" range-offset)))
                   (setq offset range-offset)))
        (query (if (functionp query)
                   (push (list query nil nil) result)
                 (when (null embed)
                   (signal 'treesit-error (list "Value of :embed option can=
not be omitted")))
                 (when (null host)
                   (signal 'treesit-error (list "Value of :host option cann=
ot be omitted")))
                 (push (list (treesit-query-compile host query)
                             embed local offset)
                       result))
               (setq host nil embed nil offset nil local nil))))
    (nreverse result)))

(defun treesit--merge-ranges (old-ranges new-ranges start end)
  "Merge OLD-RANGES and NEW-RANGES, discarding ranges between START and END.
OLD-RANGES and NEW-RANGES are lists of cons of the form (BEG . END).
When merging the two ranges, if a range in OLD-RANGES intersects with
another range in NEW-RANGES, discard the one in OLD-RANGES and
keep the one in NEW-RANGES.  Also discard any range in OLD-RANGES
that intersects the region marked by START and END.

Return the merged list of ranges."
  (let ((result nil))
    (while (and old-ranges new-ranges)
      (let ((new-beg (caar new-ranges))
            (new-end (cdar new-ranges))
            (old-beg (caar old-ranges))
            (old-end (cdar old-ranges)))
        (cond
         ;; Old range intersects with START-END, discard.
         ((and (< start old-end)
               (< old-beg end))
          (setq old-ranges (cdr old-ranges)))
         ;; New range and old range don't intersect, new comes
         ;; before, push new.
         ((<=3D new-end old-beg)
          (unless (eq new-beg new-end)
            (push (car new-ranges) result))
          (setq new-ranges (cdr new-ranges)))
         ;; New range and old range don't intersect, old comes
         ;; before, push old.
         ((<=3D old-end new-beg)
          (unless (eq old-beg old-end)
            (push (car old-ranges) result))
          (setq old-ranges (cdr old-ranges)))
         (t ;; New and old range intersect, discard old.
          (setq old-ranges (cdr old-ranges))))))
    ;; At this point, either old-ranges has left-over or new-ranges has
    ;; left-over, but not both.
    (while old-ranges
      ;; For each left-over old range, push to result unless it
      ;; intersects with START-END.
      (let ((old-beg (caar old-ranges))
            (old-end (cdar old-ranges)))
        (unless (or (and (< start old-end)
                         (< old-beg end))
                    (eq old-beg old-end))
          (push (car old-ranges) result)))
      (setq old-ranges (cdr old-ranges)))
    ;; Unconditionally push left-over new ranges to result.
    (while new-ranges
      (unless (eq (caar new-ranges) (cdar new-ranges))
        (push (car new-ranges) result))
      (setq new-ranges (cdr new-ranges)))
    (nreverse result)))

;; TODO: truncate ranges that exceeds START and END instead of
;; discarding them.  Merge into treesit--merge-ranges so we don't loop
;; over the ranges twice (might be premature optimization tho).
(defun treesit--clip-ranges (ranges start end)
  "Clip RANGES in between START and END.
RANGES is a list of ranges of the form (BEG . END).  Ranges
outside of the region between START and END are thrown away, and
those inside are kept."
  (cl-loop for range in ranges
           if (<=3D start (car range) (cdr range) end)
           collect range))

(defun treesit-local-parsers-at (&optional pos language with-host)
  "Return all the local parsers at POS.

POS defaults to point.
Local parsers are those which only parse a limited region marked
by an overlay with non-nil `treesit-parser' property.
If LANGUAGE is non-nil, only return parsers for LANGUAGE.

If WITH-HOST is non-nil, return a list of (PARSER . HOST-PARSER)
instead.  HOST-PARSER is the host parser which created the local
PARSER."
  (let ((res nil))
    (dolist (ov (overlays-at (or pos (point))))
      (when-let* ((parser (overlay-get ov 'treesit-parser))
                  (host-parser (overlay-get ov 'treesit-host-parser)))
        (when (or (null language)
                  (eq (treesit-parser-language parser)
                      language))
          (push (if with-host (cons parser host-parser) parser) res))))
    (nreverse res)))

(defun treesit-local-parsers-on (&optional beg end language with-host)
  "Return all the local parsers between BEG END.

BEG and END default to the beginning and end of the buffer's
accessible portion.
Local parsers are those which have an `embedded' tag, and only parse
a limited region marked by an overlay with a non-nil `treesit-parser'
property.  If LANGUAGE is non-nil, only return parsers for LANGUAGE.

If WITH-HOST is non-nil, return a list of (PARSER . HOST-PARSER)
instead.  HOST-PARSER is the host parser which created the local
PARSER."
  (let ((res nil))
    (dolist (ov (overlays-in (or beg (point-min)) (or end (point-max))))
      (when-let* ((parser (overlay-get ov 'treesit-parser))
                  (host-parser (overlay-get ov 'treesit-host-parser)))
        (when (or (null language)
                  (eq (treesit-parser-language parser)
                      language))
          (push (if with-host (cons parser host-parser) parser) res))))
    (nreverse res)))

(defun treesit--cleanup-local-range-overlays (modified-tick beg end)
  "Cleanup overlays used to mark local parsers between BEG and END.

=46or every local parser overlay between BEG and END, if its
`treesit-parser-ov-timestamp' is smaller than MODIFIED-TICK, delete
it."
  (dolist (ov (overlays-in beg end))
    (when-let* ((ov-timestamp
                 (overlay-get ov 'treesit-parser-ov-timestamp)))
      (when (< ov-timestamp modified-tick)
        (when-let* ((local-parser (overlay-get ov 'treesit-parser)))
          (treesit-parser-delete local-parser))
        (delete-overlay ov)))))

(defun treesit--update-ranges-local
    (query embedded-lang modified-tick &optional beg end)
  "Update range for local parsers between BEG and END.
Use QUERY to get the ranges, and make sure each range has a local
parser for EMBEDDED-LANG.

The local parser is stored in an overlay, in the `treesit-parser'
property, the host parser is stored in the `treesit-host-parser'
property.

When this function touches an overlay, it sets the
`treesit-parser-ov-timestamp' property of the overlay to
MODIFIED-TICK.  This will help Emacs garbage-collect overlays that
aren't in use anymore."
  ;; Update range.
  (let* ((host-lang (treesit-query-language query))
         (host-parser (treesit-parser-create host-lang))
         (ranges (treesit-query-range host-parser query beg end)))
    (pcase-dolist (`(,beg . ,end) ranges)
      (let ((has-parser nil))
        (setq
         has-parser
         (catch 'done
           (dolist (ov (overlays-in beg end) nil)
             ;; Update range of local parser.
             (when-let* ((embedded-parser (overlay-get ov 'treesit-parser))
                         (parser-lang (treesit-parser-language
                                       embedded-parser)))
               (when (eq parser-lang embedded-lang)
                 (treesit-parser-set-included-ranges
                  embedded-parser `((,beg . ,end)))
                 (move-overlay ov beg end)
                 (overlay-put ov 'treesit-parser-ov-timestamp
                              modified-tick)
                 (throw 'done t))))))
        ;; Create overlay and local parser.
        (when (not has-parser)
          (let ((embedded-parser (treesit-parser-create
                                  embedded-lang nil t 'embedded))
                (ov (make-overlay beg end nil nil t)))
            (overlay-put ov 'treesit-parser embedded-parser)
            (overlay-put ov 'treesit-host-parser host-parser)
            (overlay-put ov 'treesit-parser-ov-timestamp
                         modified-tick)
            (treesit-parser-set-included-ranges
             embedded-parser `((,beg . ,end)))))))))

(defun treesit-update-ranges (&optional beg end)
  "Update the ranges for each language in the current buffer.
If BEG and END are non-nil, only update parser ranges in that
region."
  (let ((modified-tick (buffer-chars-modified-tick))
        (beg (or beg (point-min)))
        (end (or end (point-max))))
    ;; When updating ranges, we want to avoid querying the whole buffer
    ;; which could be slow in very large buffers.  Instead, we only
    ;; query for nodes that intersect with the region between BEG and
    ;; END.  Also, we only update the ranges intersecting BEG and END;
    ;; outside of that region we inherit old ranges.
    (dolist (setting treesit-range-settings)
      (let ((query (nth 0 setting))
            (language (nth 1 setting))
            (local (nth 2 setting))
            (offset (nth 3 setting)))
        (cond
         ((functionp query) (funcall query beg end))
         (local
          (treesit--update-ranges-local
           query language modified-tick beg end))
         (t
          (let* ((host-lang (treesit-query-language query))
                 (parser (treesit-parser-create language))
                 (old-ranges (treesit-parser-included-ranges parser))
                 (new-ranges (treesit-query-range
                              host-lang query beg end offset))
                 (set-ranges (treesit--clip-ranges
                              (treesit--merge-ranges
                               old-ranges new-ranges beg end)
                              (point-min) (point-max))))
            (dolist (parser (treesit-parser-list nil language))
              (treesit-parser-set-included-ranges
               parser (or set-ranges
                          ;; When there's no range for the embedded
                          ;; language, set it's range to a dummy (1
                          ;; . 1), otherwise it would be set to the
                          ;; whole buffer, which is not what we want.
                          `((,(point-min) . ,(point-min)))))))))))

    (treesit--cleanup-local-range-overlays modified-tick beg end)))

(defun treesit-parser-range-on (parser beg &optional end)
  "Check if PARSER's range covers the portion between BEG and END.

If it does, return the range covering that portion in the form
of (RANGE-BEG . RANGE-END), if not, return nil.  If nil or
omitted, default END to BEG."
  (let ((ranges (treesit-parser-included-ranges parser))
        (end (or end beg)))
    (if (null ranges)
        (cons (point-min) (point-max))
      (cl-loop for rng in ranges
               if (<=3D (car rng) beg end (cdr rng))
               return rng
               finally return nil))))

;;; Language display name

;; The entries are sorted by `sort-lines'.
(defvar treesit-language-display-name-alist
  '(
    (charp . "C#")
    (cmake . "CMake")
    (cpp . "C++")
    (gomod . "Go Mod")
    (heex . "HEEx")
    (json . "JSON")
    (php . "PHP")
    (tsx . "TSX")
    )
  "An alist mapping language symbols to their display names.

Used by `treesit-language-display-name'.  If there's no mapping for a
lamguage in this alist, `treesit-language-display-name' converts the
symbol to the display name by capitalizing the first letter of the
symbol's name.  Thus, languages like Java, Javascript, Rust don't need
an entry in this variable.")

(defun treesit-language-display-name (language)
  "Return the display name (a string) of LANGUAGE (a symbol).

If LANGUAGE has an entry in `treesit-language-display-name-alist', use
the display name from there.  Otherwise, capitalize the first letter of
LANGUAGE's name and return the resulting string."
  (or (alist-get language treesit-language-display-name-alist)
      (capitalize (symbol-name language))))

;;; Fontification

(define-error 'treesit-font-lock-error
              "Generic tree-sitter font-lock error"
              'treesit-error)

;; The primary parser will be access frequently (after each re-parse,
;; before redisplay, etc, see
;; `treesit--font-lock-mark-ranges-to-fontify'), so we don't want to
;; allow it to be a callback function which returns the primary parser
;; (it might be slow).  It's not something that needs to be dynamic
;; anyway.
(defvar-local treesit-primary-parser nil
  "The primary parser for this buffer.

The primary parser should be a parser that parses the entire buffer, as
opposed to embedded parsers which parses only part of the buffer.")

(defvar-local treesit-font-lock-settings nil
  "A list of SETTINGs for treesit-based fontification.

Use `treesit-font-lock-rules' to set this variable.  The exact format of
each individual SETTING is considered internal and will change in the
future.  Use `treesit-font-lock-setting-query',
`treesit-font-lock-setting-enable', etc, to access each field.

Below information is considered internal and only provided to help
debugging:

Currently each SETTING has the form:

    (QUERY ENABLE FEATURE OVERRIDE REVERSE)

QUERY must be a compiled query.  See Info node `(elisp)Pattern
Matching' for how to write a query and compile it.

=46or SETTING to be activated for font-lock, ENABLE must be t.  To
disable this SETTING, set ENABLE to nil.

=46EATURE is the \"feature name\" of the query.  Users can control
which features are enabled with `treesit-font-lock-level' and
`treesit-font-lock-feature-list'.

OVERRIDE is the override flag for this query.  Its value can be
t, nil, append, prepend, keep.  See more in
`treesit-font-lock-rules'.

If REVERSED is t, enable the QUERY when FEATURE is not in the feature
list.")

;; Follow cl-defstruct naming conventions, in case we use cl-defstruct
;; in the future.
(defsubst treesit-font-lock-setting-query (setting)
  "Return the QUERY of SETTING in `treesit-font-lock-settings'."
  (nth 0 setting))

(defsubst treesit-font-lock-setting-enable (setting)
  "Return the ENABLE flag of SETTING in `treesit-font-lock-settings'."
  (nth 1 setting))

(defsubst treesit-font-lock-setting-feature (setting)
  "Return the FEATURE symbol of SETTING in `treesit-font-lock-settings'."
  (nth 2 setting))

(defsubst treesit-font-lock-setting-override (setting)
  "Return the OVERRIDE flag of SETTING in `treesit-font-lock-settings'."
  (nth 3 setting))

(defsubst treesit-font-lock-setting-reversed (setting)
  "Return the REVERSED flag of SETTING in `treesit-font-lock-settings'."
  (nth 4 setting))

(defsubst treesit--font-lock-setting-clone-enable (setting)
  "Return enabled SETTING."
  (let ((new-setting (copy-tree setting)))
    (setf (nth 1 new-setting) t)
    new-setting))

(defun treesit--font-lock-level-setter (sym val)
  "Custom setter for `treesit-font-lock-level'.
Set the default value of SYM to VAL, recompute fontification
features and refontify for every buffer where tree-sitter-based
fontification is enabled."
  (set-default sym val)
  (when (treesit-available-p)
    (dolist (buffer (buffer-list))
      (with-current-buffer buffer
        ;; FIXME: This doesn't re-run major mode hooks, meaning any
        ;; customization done in major mode hooks (e.g., with
        ;; `treesit-font-lock-recompute-features') is lost.
        (when treesit-font-lock-settings
          (treesit-font-lock-recompute-features)
          (treesit-font-lock-fontify-region
           (point-min) (point-max)))))))

(defcustom treesit-font-lock-level 3
  "Decoration level to be used by tree-sitter fontifications.

Major modes categorize their fontification features into levels,
from 1 which is the absolute minimum, to 4 that yields the maximum
fontifications.

Level 1 usually contains only comments and definitions.
Level 2 usually adds keywords, strings, data types, etc.
Level 3 usually represents full-blown fontifications, including
assignments, constants, numbers and literals, etc.
Level 4 adds everything else that can be fontified: delimiters,
operators, brackets, punctuation, all functions, properties,
variables, etc.

The value of this variable can be either a number representing a level,
or an alist of (MAJOR-MODE . LEVEL), where MAJOR-MODE is major mode
symbols, or t (meaning the default), and LEVEL is the font-lock level
for that mode.  For example,

    ((c-ts-mode . 3) (c++-ts-mode . 4) (t . 3))

Major mode is checked with `derived-mode-p'.

In addition to the decoration level, individual features can be
turned on/off by calling `treesit-font-lock-recompute-features'.
Changing the decoration level requires calling
`treesit-font-lock-recompute-features' to have an effect, unless
done via `customize-variable'.

To see which syntactical categories are fontified by each level
in a particular major mode, examine the buffer-local value of the
variable `treesit-font-lock-feature-list'."
  :type 'integer
  :set #'treesit--font-lock-level-setter
  :version "29.1")

(defvar-local treesit--font-lock-query-expand-range (cons 0 0)
  "The amount to expand the start and end of the region when fontifying.
This should be a cons cell (START . END).  When fontifying a
buffer, Emacs will move the start of the query range backward by
START amount, and the end of the query range by END amount.  Both
START and END should be positive integers or 0.  This doesn't
affect the fontified range.")

(defvar-local treesit-font-lock-feature-list nil
  "A list of lists of feature symbols.

Use `treesit-font-lock-recompute-features' and
`treesit-font-lock-level' to configure enabled features.

Each sublist represents a decoration level.
`treesit-font-lock-level' controls which levels are activated.

Inside each sublist are feature symbols, which correspond to the
:feature value of a query defined in `treesit-font-lock-rules'.
Removing a feature symbol from this list disables the
corresponding query during font-lock.

Common feature names (for general programming languages) include
definition, type, assignment, builtin, constant, keyword,
string-interpolation, comment, doc, string, operator, property,
preprocessor, escape-sequence, key (in key-value pairs).  Major
modes are free to subdivide or extend on these common features.
See the manual for more explanations on some of the features.

=46or changes to this variable to take effect, run
`treesit-font-lock-recompute-features'.")

(defun treesit-font-lock-rules (&rest query-specs)
  "Return a value suitable for `treesit-font-lock-settings'.

QUERY-SPECS is a series of QUERY-SPECs.  Each QUERY-SPEC is a
QUERY preceded by multiple pairs of :KEYWORD and VALUE:

   :KEYWORD VALUE... QUERY

QUERY is a tree-sitter query in either the string, s-expression
or compiled form.  For each query, captured nodes are highlighted
with the capture name as its face.

:KEYWORD and VALUE pairs preceding a QUERY add meta information
to QUERY.  For example,

    (treesit-font-lock-rules
     :language \\=3D'javascript
     :override t
     :feature\\=3D'constant
     \\=3D'((true) @font-lock-constant-face
       (false) @font-lock-constant-face)
     :language \\=3D'html
     :feature \\=3D'script
     \"(script_element) @font-lock-builtin-face\")

=46or each QUERY, a :language keyword and a :feature keyword are
required.  Each query's :feature is a symbol summarizing what the
query fontifies.  It is used to allow users to enable/disable
certain features.  See `treesit-font-lock-feature-list' for more.
Other keywords include:

  KEYWORD    VALUE      DESCRIPTION
  :override  nil        If the region already has a face,
                        discard the new face.
             t          Always apply the new face.
             `append'   Append the new face to existing ones.
             `prepend'  Prepend the new face to existing ones.
             `keep'     Fill-in regions without an existing face.
  :reversed  t          Enable the query only if the feature is
                        NOT in feature list.
  :default-language  LANGUAGE  Every QUERY after this keyword
                               will use LANGUAGE by default.

Capture names in QUERY should be face names like
`font-lock-keyword-face'.  The captured node will be fontified
with that face.

Capture names can also be function names, in which case the
function will be called with the following argument list:

    (NODE OVERRIDE START END &rest _)

where NODE is the tree-sitter node object, OVERRIDE is the
override option of that rule, and START and END specify the region
to be fontified.  This function should accept more arguments as
optional arguments for future extensibility, and it shouldn't
fontify text outside the region given by START and END.

If a capture name is both a face and a function, the face takes
priority.  If a capture name is not a face name nor a function
name, it is ignored."
  ;; Other tree-sitter function don't tend to be called unless
  ;; tree-sitter is enabled, which means tree-sitter must be compiled.
  ;; But this function is usually call in `defvar' which runs
  ;; regardless whether tree-sitter is enabled.  So we need this
  ;; guard.
  (when (treesit-available-p)
    (let (;; Tracks the current :language/:override/:toggle/:level value
          ;; that following queries will apply to.
          current-language current-override
          current-feature
          ;; DEFAULT-LANGUAGE will be chosen when current-language is
          ;; not set.
          default-language
          current-reversed
          ;; The list this function returns.
          (result nil))
      (while query-specs
        (let ((token (pop query-specs)))
          (pcase token
            ;; (1) Process keywords.
            (:default-language
             (let ((lang (pop query-specs)))
               (when (or (not (symbolp lang)) (null lang))
                 (signal 'treesit-font-lock-error
                         `("Value of :default-language should be a symbol"
                           ,lang)))
               (setq default-language lang)))
            (:language
             (let ((lang (pop query-specs)))
               (when (or (not (symbolp lang)) (null lang))
                 (signal 'treesit-font-lock-error
                         `("Value of :language should be a symbol"
                           ,lang)))
               (setq current-language lang)))
            (:override
             (let ((flag (pop query-specs)))
               (when (not (memq flag '(t nil append prepend keep)))
                 (signal 'treesit-font-lock-error
                         `("Value of :override should be one of t, nil, app=
end, prepend, keep"
                           ,flag))
                 (signal 'wrong-type-argument
                         `((or t nil append prepend keep)
                           ,flag)))
               (setq current-override flag)))
            (:feature
             (let ((var (pop query-specs)))
               (when (or (not (symbolp var))
                         (memq var '(t nil)))
                 (signal 'treesit-font-lock-error
                         `("Value of :feature should be a symbol"
                           ,var)))
               (setq current-feature var)))
            (:reversed
             (let ((var (pop query-specs)))
               (when (not (memq var '(t nil)))
                 (signal 'treesit-font-lock-error
                         `("Value of :reversed can only be t or nil"
                           ,var)))
               (setq current-reversed var)))
            ;; (2) Process query.
            ((pred treesit-query-p)
             (let ((lang (or default-language current-language)))
               (when (null lang)
                 (signal 'treesit-font-lock-error
                         `("Language unspecified, use :language keyword or =
:default-language to specify a language for this query" ,token)))
               (when (null current-feature)
                 (signal 'treesit-font-lock-error
                         `("Feature unspecified, use :feature keyword to sp=
ecify the feature name for this query" ,token)))
               (if (treesit-compiled-query-p token)
                   (push `(,lang token) result)
                 (push `(,(treesit-query-compile lang token)
                         t
                         ,current-feature
                         ,current-override
                         ,current-reversed)
                       result))
               ;; Clears any configurations set for this query.
               (setq current-language nil
                     current-override nil
                     current-feature nil
                     current-reversed nil)))
            (_ (signal 'treesit-font-lock-error
                       `("Unexpected value" ,token))))))
      (nreverse result))))

;; `font-lock-fontify-region-function' has the LOUDLY argument, but
;; `jit-lock-functions' doesn't pass that argument.  So even if we set
;; `font-lock-verbose' to t, if jit-lock is enabled (and it's almost
;; always is), we don't get debug messages.  So we add our own.
(defvar treesit--font-lock-verbose nil
  "If non-nil, print debug messages when fontifying.")

(defun treesit--compute-font-lock-level (level)
  "Compute the font-lock level for the current major mode.

LEVEL should be the value of `treesit-font-lock-level'.  Return a number
representing the font-lock level for the current major mode.  If there's
no match, return 3."
  (if (numberp level)
      level
    (catch 'found
      (dolist (config level)
        (let ((mode (car config))
              (num (cdr config)))
          (when (derived-mode-p mode)
            (throw 'found num))))
      (or (alist-get t level)
          3))))

(defun treesit-font-lock-recompute-features
    (&optional add-list remove-list language)
  "Enable/disable font-lock features.

Enable each feature in ADD-LIST, disable each feature in
REMOVE-LIST.

If both ADD-LIST and REMOVE-LIST are omitted, recompute each
feature according to `treesit-font-lock-feature-list' and
`treesit-font-lock-level'.  If the value of `treesit-font-lock-level',
is N, then the features in the first N sublists of
`treesit-font-lock-feature-list' are enabled, and the rest of
the features are disabled.

ADD-LIST and REMOVE-LIST are lists of feature symbols.  The
same feature symbol cannot appear in both lists; the function
signals the `treesit-font-lock-error' error if that happens.

If LANGUAGE is non-nil, only compute features for that language,
and leave settings for other languages unchanged."
  (when-let* ((intersection (cl-intersection add-list remove-list)))
    (signal 'treesit-font-lock-error
            (list "ADD-LIST and REMOVE-LIST contain the same feature"
                  intersection)))
  (let* ((level (treesit--compute-font-lock-level
                 treesit-font-lock-level))
         (base-features (cl-loop
                         for idx =3D 0 then (1+ idx)
                         for features in treesit-font-lock-feature-list
                         if (or (eq level t)
                                (>=3D level (1+ idx)))
                         append features))
         (features (cl-set-difference (cl-union base-features add-list)
                                      remove-list))
         ;; If additive non-nil, we are configuring on top of the
         ;; existing configuration, if nil, we are resetting
         ;; everything according to `treesit-font-lock-feature-list'.
         (additive (or add-list remove-list)))
    (cl-loop for idx =3D 0 then (1+ idx)
             for setting in treesit-font-lock-settings
             for lang =3D (treesit-query-language
                         (treesit-font-lock-setting-query setting))
             for feature =3D (treesit-font-lock-setting-feature setting)
             for current-value =3D (treesit-font-lock-setting-enable settin=
g)
             for reversed =3D (treesit-font-lock-setting-reversed setting)
             ;; Set the ENABLE flag for the setting if its language is
             ;; relevant.
             if (or (null language)
                    (eq language lang))
             do (setf (nth 1 (nth idx treesit-font-lock-settings))
                      (cond
                       ((not additive)
                        (if (not reversed)
                            (if (memq feature features) t nil)
                          (if (memq feature features) nil t)))
                       ((memq feature add-list) t)
                       ((memq feature remove-list) nil)
                       (t current-value))))))

(defun treesit-merge-font-lock-feature-list (features-list-1 features-list-=
2)
  "Merge two tree-sitter font lock feature lists.
Returns a new font lock feature list with no duplicates in the same level.
It can be used to merge font lock feature lists in a multi-language major m=
ode.
=46EATURES-LIST-1 and FEATURES-LIST-2 are list of lists of feature symbols."
    (let ((result nil)
	(features-1 (car features-list-1))
	(features-2 (car features-list-2)))
    (while (or features-1 features-2)
      (cond
       ((and features-1 (not features-2)) (push features-1 result))
       ((and (not features-1) features-2) (push features-2 result))
       ((and features-1 features-2) (push (cl-union features-1 features-2) =
result)))
      (setq features-list-1 (cdr features-list-1)
	    features-list-2 (cdr features-list-2)
	    features-1 (car features-list-1)
            features-2 (car features-list-2)))
    (nreverse result)))

(defun treesit-replace-font-lock-feature-settings (new-settings settings)
  "Replaces :feature in SETTINGS with :feature from NEW-SETTINGS.
Both SETTINGS and NEW-SETTINGS must be a value suitable for
`treesit-font-lock-settings'.
Return a value suitable for `treesit-font-lock-settings'"
  (let ((result nil))
    (dolist (new-setting new-settings)
      (let ((new-feature (treesit-font-lock-setting-feature new-setting)))
	(dolist (setting settings)
	  (let ((feature (treesit-font-lock-setting-feature setting)))
	    (if (eq new-feature feature)
		(push new-setting result)
	      (push setting result))))))
    (nreverse result)))

(defun treesit-add-font-lock-rules (rules &optional how feature)
  "Add font-lock RULES to the current buffer.

RULES should be the return value of `treesit-font-lock-rules'.  RULES
will be enabled and added to `treesit-font-lock-settings'.

HOW can be either :before or :after.  If HOW is :before, prepend RULES
before all other existing font-lock rules in
`treesit-font-lock-settings'; if :after or omitted, append RULES after
all existing rules.

If FEATURE is non-nil, add RULES before/after rules for FEATURE.  See
docstring of `treesit-font-lock-rules' for what is a feature."
  (let ((rules (seq-map #'treesit--font-lock-setting-clone-enable rules))
        (feature-idx
         (when feature
           (cl-position-if
            (lambda (setting)
              (eq (treesit-font-lock-setting-feature setting) feature))
            treesit-font-lock-settings))))
    (pcase (cons how feature)
      ((or '(:after . nil) '(nil . nil))
       (setq treesit-font-lock-settings
             (append treesit-font-lock-settings rules)))
      ('(:before . nil)
       (setq treesit-font-lock-settings
             (append rules treesit-font-lock-settings)))
      (`(:after . ,_feature)
       (setf (nthcdr (1+ feature-idx) treesit-font-lock-settings)
             (append rules
                     (nthcdr (1+ feature-idx)
                             treesit-font-lock-settings))))
      (`(:before . ,_feature)
       (setf (nthcdr feature-idx treesit-font-lock-settings)
             (append rules
                     (nthcdr feature-idx treesit-font-lock-settings)))))))

(defun treesit-validate-font-lock-rules (settings)
  "Validate font-lock rules in SETTINGS before major mode starts.

If the tree-sitter grammar currently installed on the system is
incompatible with the major mode's font-lock rules, this procedure will
detect the problematic rule, disable it temporarily, and notify the
user."
  (let ((faulty-features ()))
    (dolist (setting settings)
      (let* ((query (treesit-font-lock-setting-query setting))
             (lang (treesit-query-language query))
             (enabled (treesit-font-lock-setting-enable setting)))
        (when (and enabled
                   (condition-case nil
                       (progn
                         (treesit-query-compile lang query 'eager)
                         nil)
                     (treesit-query-error t)))
          (push (cons (treesit-font-lock-setting-feature setting)
                      lang)
                faulty-features))))
    (when faulty-features
      (treesit-font-lock-recompute-features
       nil (mapcar #'car faulty-features))
      (let* ((languages
              (string-join
               (delete-dups (mapcar (lambda (feat)
                                      (format "tree-sitter-%s" (cdr feat)))
                                    faulty-features))
               ", "))
             (features (string-join
                        (mapcar
                         (lambda (feat)
                           (format "- `%s' for %s"
                                   (car feat) (cdr feat)))
                         faulty-features)
                        ",\n")))
        (display-warning
         'treesit-font-lock-rules-mismatch
         (format "Emacs cannot compile every font-lock rules because a mism=
atch between the grammar and the rules.  This is most likely due to a misma=
tch between the font-lock rules defined by the major mode and the tree-sitt=
er grammar.\n\nThis error can be fixed by either downgrading the grammar (%=
s) on your system, or upgrading the major mode package.  The following are =
the temporarily disabled features:\n\n%s."
                 languages features))))))

(defun treesit-fontify-with-override
    (start end face override &optional bound-start bound-end)
  "Apply FACE to the region between START and END.
OVERRIDE can be nil, t, `append', `prepend', or `keep'.
See `treesit-font-lock-rules' for their semantics.

If BOUND-START and BOUND-END are non-nil, only fontify the region
in between them."
  (when (or (null bound-start) (null bound-end)
            (and (<=3D bound-start end)
                 (>=3D bound-end start)))
    (when (and bound-start bound-end)
      (setq start (max bound-start start)
            end (min bound-end end)))
    (pcase override
      ('nil (unless (text-property-not-all start end 'face nil)
              (put-text-property start end 'face face)))
      ('t (put-text-property start end 'face face))
      ('append (font-lock-append-text-property
                start end 'face face))
      ('prepend (font-lock-prepend-text-property
                 start end 'face face))
      ('keep (font-lock-fillin-text-property
              start end 'face face))
      (_ (signal 'treesit-font-lock-error
                 (list
                  "Unrecognized value of :override option"
                  override))))))

(defun treesit--set-nonsticky (start end sym &optional remove)
  "Set `rear-nonsticky' property between START and END.
Set the property to a list containing SYM.  If there is already a
list, add SYM to that list.  If REMOVE is non-nil, remove SYM
instead."
  (let* ((prop (get-text-property start 'rear-nonsticky))
         (new-prop
          (pcase prop
            ((pred listp) ; PROP is a list or nil.
             (if remove
                 (remove sym prop)
               ;; We should make sure PORP doesn't contain SYM, but
               ;; whatever.
               (cons sym prop)))
            ;; PROP is t.
            (_ (if remove
                   nil
                 (list sym))))))
    (if (null new-prop)
        (remove-text-properties start end '(rear-nonsticky nil))
      (put-text-property start end 'rear-nonsticky new-prop))))

(defun treesit--children-covering-range (node start end)
  "Return a list of children of NODE covering a range.
The range is between START and END."
  (if-let* ((child (treesit-node-first-child-for-pos node start))
            (result (list child)))
      (progn
        (while (and child (< (treesit-node-end child) end)
                    (setq child (treesit-node-next-sibling child)))
          (push child result))
        (nreverse result))
    (list node)))

(defun treesit--children-covering-range-recurse
    (node start end threshold &optional limit)
  "Return a list of children of NODE covering a range.

Recursively go down the parse tree and collect children, until
all nodes in the returned list are smaller than THRESHOLD.  The
range is between START and END.

LIMIT is the recursion limit, which defaults to 100."
  (let* ((child (treesit-node-first-child-for-pos node start))
         (limit (or limit 100))
         result)
    ;; If LIMIT is exceeded, we are probably seeing the erroneously
    ;; tall tree, in that case, just give up.
    (while (and (> limit 0) child (<=3D (treesit-node-start child) end))
      ;; If child still too large, recurse down.  Otherwise collect
      ;; child.
      (if (> (- (treesit-node-end child)
                (treesit-node-start child))
             threshold)
          (dolist (r (treesit--children-covering-range-recurse
                      child start end threshold (1- limit)))
            (push r result))
        (push child result))
      (setq child (treesit-node-next-sibling child)))
    ;; If NODE has no child, keep NODE.  If LIMIT is exceeded, return
    ;; nil.
    (or result (and (> limit 0) (list node)))))

(defsubst treesit--node-length (node)
  "Return the length of the text of NODE."
  (- (treesit-node-end node) (treesit-node-start node)))

(defvar-local treesit--font-lock-fast-mode 'unspecified
  "If this variable is t, change the way we query so it's faster.
This is not a general optimization and should be RARELY needed!
See comments in `treesit-font-lock-fontify-region' for more
detail.")

;; Some details worth explaining:
;;
;; 1. When we apply face to a node, we clip the face into the
;; currently fontifying region, this way we don't overwrite faces
;; applied by regexp-based font-lock.  The clipped part will be
;; fontified fine when Emacs fontifies the region containing it.
;;
;; 2. If you insert an ending quote into a buffer, jit-lock only wants
;; to fontify that single quote, and (treesit-node-on start end) will
;; give you that quote node.  We want to capture the string and apply
;; string face to it, but querying on the quote node will not give us
;; the string node.  So we don't use treesit-node-on: using the root
;; node with a restricted range is very fast anyway (even in large
;; files of size ~10MB).  Plus, querying the result of
;; `treesit-node-on' could still miss patterns even if we use some
;; heuristic to enlarge the node (how much to enlarge? to which
;; extent?), it's much safer to just use the root node.
;;
;; Sometimes the source file has some errors that cause tree-sitter to
;; parse it into a enormously tall tree (10k levels tall).  In that
;; case querying the root node is very slow.  So we try to get
;; top-level nodes and query them.  This ensures that querying is fast
;; everywhere else, except for the problematic region.  (Bug#59415).
;;
;; Some other time the source file has a top-level node that contains
;; a huge number of immediate children (say, 10k children), querying
;; that node is also very slow, so instead of getting the top-level
;; node, we recursively go down the tree to find nodes that cover the
;; region but are reasonably small.  (Bug#59738).
;;
;; 3. It is possible to capture a node that's completely outside the
;; region between START and END: as long as the whole pattern
;; intersects the region, all the captured nodes in that pattern are
;; returned.  If the node is outside of that region, (max node-start
;; start) and friends return bad values, so we filter them out.
;; However, we don't filter these nodes out if a function will process
;; the node, because it could (and often do) fontify the relatives of
;; the captured node, not just the node itself.  If we took out those
;; nodes author of those functions would be very confused.
(defun treesit-font-lock-fontify-region (start end &optional loudly)
  "Fontify the region between START and END.
If LOUDLY is non-nil, display some debugging information."
  (when (or loudly treesit--font-lock-verbose)
    (message "Fontifying region: %s-%s" start end))
  (treesit-update-ranges start end)
  (font-lock-unfontify-region start end)
  (let* ((local-parsers (treesit-local-parsers-on start end))
         (global-parsers (treesit-parser-list))
         (root-nodes
          (mapcar #'treesit-parser-root-node
                  (append local-parsers global-parsers))))
    ;; Can't we combine all the queries in each setting into one big
    ;; query? That should make font-lock faster? I tried, it shaved off
    ;; 1ms in xdisp.c, and 0.3ms in a small C file (for typing a single
    ;; character), not worth it.  --yuan
    (dolist (setting treesit-font-lock-settings)
      (let* ((query (nth 0 setting))
             (enable (nth 1 setting))
             (override (nth 3 setting))
             (language (treesit-query-language query))
             (root-nodes (cl-remove-if-not
                          (lambda (node)
                            (eq (treesit-node-language node) language))
                          root-nodes)))

        ;; Use deterministic way to decide whether to turn on "fast
        ;; mode". (See bug#60691, bug#60223.)
        (when (eq treesit--font-lock-fast-mode 'unspecified)
          (pcase-let ((`(,max-depth ,max-width)
                       (treesit-subtree-stat
                        (treesit-buffer-root-node language))))
            (if (or (> max-depth 100) (> max-width 4000))
                (setq treesit--font-lock-fast-mode t)
              (setq treesit--font-lock-fast-mode nil))))

        ;; Only activate if ENABLE flag is t.
        (when-let*
            ((activate (eq t enable))
             (nodes (if (eq t treesit--font-lock-fast-mode)
                        (mapcan
                         (lambda (node)
                           (treesit--children-covering-range-recurse
                            node start end (* 4 jit-lock-chunk-size)))
                         root-nodes)
                      root-nodes)))
          (ignore activate)

          ;; Query each node.
          (dolist (sub-node nodes)
            (treesit--font-lock-fontify-region-1
             sub-node query start end override loudly))))))
  `(jit-lock-bounds ,start . ,end))

(defun treesit--font-lock-fontify-region-1 (node query start end override l=
oudly)
  "Fontify the region between START and END by querying NODE with QUERY.

If OVERRIDE is non-nil, override existing faces, if LOUDLY is
non-nil, print debugging information."
  (let* ((delta-start (car treesit--font-lock-query-expand-range))
         (delta-end (cdr treesit--font-lock-query-expand-range))
         (captures (treesit-query-capture
                    node query
                    (max (- start delta-start) (point-min))
                    (min (+ end delta-end) (point-max)))))

    ;; For each captured node, fontify that node.
    (with-silent-modifications
      (dolist (capture captures)
        (let* ((face (car capture))
               (node (cdr capture))
               (node-start (treesit-node-start node))
               (node-end (treesit-node-end node)))

          ;; If node is not in the region, take them out.  See
          ;; comment #3 above for more detail.
          (if (and (facep face)
                   (or (>=3D start node-end) (>=3D node-start end)))
              (when (or loudly treesit--font-lock-verbose)
                (message "Captured node %s(%s-%s) but it is outside of font=
ifing region" node node-start node-end))

            (cond
             ((facep face)
              (treesit-fontify-with-override
               (max node-start start) (min node-end end)
               face override))
             ((functionp face)
              (funcall face node override start end)))

            ;; Don't raise an error if FACE is neither a face nor
            ;; a function.  This is to allow intermediate capture
            ;; names used for #match and #eq.
            (when (or loudly treesit--font-lock-verbose)
              (message "Fontifying text from %d to %d, Face: %s, Node: %s"
                       (max node-start start) (min node-end end)
                       face (treesit-node-type node)))))))))

(defvar-local treesit--syntax-propertize-start nil
  "If non-nil, next `syntax-propertize' should start at this position.

When tree-sitter parser reparses, it calls
`treesit--font-lock-mark-ranges-to-fontify' with the changed ranges, and
that function sets this variable to the start of the changed ranges.")

(defvar-local treesit--pre-redisplay-tick nil
  "The last `buffer-chars-modified-tick' that we've processed.
Because `pre-redisplay-functions' could be called multiple times
during a single command loop, we use this variable to debounce
calls to `treesit--pre-redisplay'.")

(defun treesit--font-lock-mark-ranges-to-fontify (ranges _parser)
  "A notifier that marks ranges that needs refontification.

=46or RANGES and PARSER see `treesit-parser-add-notifier'.

After the parser reparses, we get the changed ranges, and
1) update non-primary parsers' ranges in the changed ranges
2) mark these ranges as to-be-fontified,
3) tell `syntax-ppss' to start reparsing from the min point of the
   ranges.

We need to mark to-be-fontified ranges before redisplay starts working,
because sometimes the range edited by the user is not the only range
that needs to be refontified.  For example, when the user types the
final slash of a C block comment /* xxx */, not only do we need to
fontify the slash, but also the whole block comment, which previously
wasn't fontified as comment due to incomplete parse tree."
  (dolist (range ranges)
    ;; 1. Update ranges.
    (treesit-update-ranges (car range) (cdr range))
    ;; 2. Mark the changed ranges to be fontified.
    (when treesit--font-lock-verbose
      (message "Notifier received range: %s-%s"
               (car range) (cdr range)))
    (with-silent-modifications
      (put-text-property (car range) (cdr range) 'fontified nil))
    ;; 3. Set `treesit--syntax-propertize-start'.
    (if (null treesit--syntax-propertize-start)
        (setq treesit--syntax-propertize-start (car range))
      (setq treesit--syntax-propertize-start
            (min treesit--syntax-propertize-start (car range))))))

(defun treesit--guess-primary-parser ()
  "Guess the primary parser of the current buffer and return it.

Normally in a tree-sitter major mode, there is a primary parser that
parses the entire buffer (as opposed to embedded parsers which only
parses part of the buffer).  This function tries to find and return that
parser."
  (if treesit-range-settings
      (let ((query (car (car treesit-range-settings))))
        (if (treesit-query-p query)
            (treesit-parser-create
             (treesit-query-language query))
          (car (treesit-parser-list))))
    (car (treesit-parser-list))))

(defun treesit--pre-redisplay (&rest _)
  "Force a reparse on the primary parser and mark regions to be fontified.

The actual work is carried out by
`treesit--font-lock-mark-ranges-to-fontify', which see."
  (unless (eq treesit--pre-redisplay-tick (buffer-chars-modified-tick))
    (when treesit-primary-parser
      ;; Force a reparse on the primary parser, if everything is setup
      ;; correctly, the parser should call
      ;; `treesit--font-lock-mark-ranges-to-fontify' (which should be a
      ;; notifier function of the primary parser).
      (treesit-parser-root-node treesit-primary-parser))

    (setq treesit--pre-redisplay-tick (buffer-chars-modified-tick))))

(defun treesit--pre-syntax-ppss (start end)
  "Force reparse and consequently run all notifiers.

Similar to font-lock, we want to update the `syntax' text
property before `syntax-ppss' starts working on the text.  We
also want to extend the to-be-propertized region to include the
whole region affected by the last reparse.

START and END mark the current to-be-propertized region."
  (treesit--pre-redisplay)
  ;; `treesit--syntax-propertize-start' is set by
  ;; `treesit--font-lock-mark-ranges-to-fontify', which is called after
  ;; each re-parser on the primary parser and in
  ;; `treesit--pre-redisplay'.
  (let ((new-start treesit--syntax-propertize-start))
    (if (and new-start (< new-start start))
        (progn
          (setq treesit--syntax-propertize-start nil)
          (cons (max new-start (point-min)) end))
      nil)))

;;; Indent

(define-error 'treesit-indent-error
              "Generic tree-sitter indentation error"
              'treesit-error)

(defvar treesit--indent-verbose nil
  "If non-nil, log progress when indenting.")

(defvar-local treesit-simple-indent-rules nil
  "A list of indent rule settings.
Each indent rule setting should be (LANGUAGE RULE...), where LANGUAGE is
a language symbol, and each RULE is of the form

    (MATCHER ANCHOR OFFSET)

MATCHER determines whether this rule applies, ANCHOR and
OFFSET together determines which column to indent to.

A MATCHER is a function that takes three arguments (NODE PARENT
BOL).  BOL is the point where we are indenting: the beginning of
line content, the position of the first non-whitespace character.
NODE is the largest (highest-in-tree) node starting at that
point.  PARENT is the parent of NODE.

If MATCHER returns non-nil, meaning the rule matches, Emacs then
uses ANCHOR to find an anchor, it should be a function that takes
the same argument (NODE PARENT BOL) and returns a point.

=46inally Emacs computes the column of that point returned by
ANCHOR and adds OFFSET to it, and indents to that column.  OFFSET
can be an integer or a variable whose value is an integer.

=46or MATCHER and ANCHOR, Emacs provides some convenient presets.
See `treesit-simple-indent-presets'.

=46or complex cases, a RULE can also be a single function.  This function
should take the same argument as MATCHER or ANCHOR.  If it matches,
return a cons (ANCHOR-POS . OFFSET), where ANCHOR-POS is a position and
OFFSET is the indent offset; if it doesn't match, return nil.")

(defun treesit--indent-prev-line-node (pos)
  "Return the largest node on the previous line of POS."
  (save-excursion
    (goto-char pos)
    (when (eq (forward-line -1) 0)
      (back-to-indentation)
      (treesit--indent-largest-node-at (point)))))

(defvar treesit-simple-indent-presets
  (list (cons 'match
              (lambda
                (&optional node-type parent-type node-field
                           node-index-min node-index-max)
                (lambda (node parent &rest _)
                  (and (pcase node-type
                         ('nil t)
                         ('null (null node))
                         (_ (string-match-p
                             node-type (or (treesit-node-type node) ""))))
                       (or (null parent-type)
                           (string-match-p
                            parent-type (treesit-node-type parent)))
                       (or (null node-field)
                           (string-match-p
                            node-field
                            (or (treesit-node-field-name node) "")))
                       (or (null node-index-min)
                           (>=3D (treesit-node-index node)
                               node-index-min))
                       (or (null node-index-max)
                           (<=3D (treesit-node-index node)
                               node-index-max))))))
        (cons 'n-p-gp
              (lambda (node-t parent-t grand-parent-t)
                (lambda (node parent &rest _)
                  (and (or (null node-t)
                           (string-match-p
                            node-t (or (treesit-node-type node) "")))
                       (or (null parent-t)
                           (string-match-p
                            parent-t (treesit-node-type parent)))
                       (or (null grand-parent-t)
                           (and
                            (treesit-node-parent parent)
                            (string-match-p
                             grand-parent-t
                             (treesit-node-type
                              (treesit-node-parent parent)))))))))
        (cons 'no-node (lambda (node &rest _) (null node)))
        (cons 'parent-is (lambda (type)
                           (lambda (_n parent &rest _)
                             (string-match-p
                              type (treesit-node-type parent)))))

        (cons 'node-is (lambda (type)
                         (lambda (node &rest _)
                           (string-match-p
                            type (or (treesit-node-type node) "")))))
        ;; FIXME: Add to manual.
        (cons 'prev-line-is (lambda (type)
                              (lambda (_n _p bol &rest _)
                                (treesit-node-match-p
                                 (treesit--indent-prev-line-node bol)
                                 type))))
        (cons 'field-is (lambda (name)
                          (lambda (node &rest _)
                            (string-match-p
                             name (or (treesit-node-field-name node) "")))))
        (cons 'comment-end (lambda (_node _parent bol &rest _)
                             (save-excursion
                               (goto-char bol)
                               (looking-at-p comment-end-skip))))
        (cons 'catch-all (lambda (&rest _) t))

        (cons 'query (lambda (pattern)
                       (lambda (node parent &rest _)
                         (cl-loop for capture
                                  in (treesit-query-capture
                                      parent pattern)
                                  if (treesit-node-eq node (cdr capture))
                                  return t
                                  finally return nil))))
        (cons 'first-sibling (lambda (_n parent &rest _)
                               (treesit-node-start
                                (treesit-node-child parent 0))))
        (cons 'nth-sibling (lambda (n &optional named)
                             (lambda (_n parent &rest _)
                               (treesit-node-start
                                (treesit-node-child parent n named)))))
        (cons 'parent (lambda (_n parent &rest _)
                        (treesit-node-start parent)))
        (cons 'comment-start
              (lambda (_n parent &rest _)
                (save-excursion
                  (goto-char (treesit-node-start parent))
                  (re-search-forward comment-start-skip)
                  (skip-syntax-backward "-")
                  (point))))
        (cons 'prev-adaptive-prefix
              (lambda (_n parent bol &rest _)
                (let (comment-start-bol
                      this-line-has-prefix)
                  (save-excursion
                    (goto-char (treesit-node-start parent))
                    (setq comment-start-bol (line-beginning-position))

                    (goto-char bol)
                    (setq this-line-has-prefix
                          (and (looking-at adaptive-fill-regexp)
                               (not (string-match-p
                                     (rx bos (* whitespace) eos)
                                     (match-string 0)))))

                    (forward-line -1)
                    (and (>=3D (point) comment-start-bol)
                         adaptive-fill-regexp
                         (looking-at adaptive-fill-regexp)
                         ;; If previous line is an empty line, don't
                         ;; indent.
                         (not (looking-at-p (rx (* whitespace) eol)))
                         ;; Return the anchor.  If the indenting line
                         ;; has a prefix and the previous line also
                         ;; has a prefix, indent to the beginning of
                         ;; prev line's prefix rather than the end of
                         ;; prev line's prefix. (Bug#61314).
                         (or (and this-line-has-prefix
                                  (match-beginning 1))
                             (match-end 0)))))))
        (cons 'grand-parent
              (lambda (_n parent &rest _)
                (treesit-node-start (treesit-node-parent parent))))
        (cons 'great-grand-parent
              (lambda (_n parent &rest _)
                (treesit-node-start
                 (treesit-node-parent
                  (treesit-node-parent parent)))))
        (cons 'parent-bol (lambda (_n parent &rest _)
                            (save-excursion
                              (goto-char (treesit-node-start parent))
                              (back-to-indentation)
                              (point))))
        (cons 'standalone-parent
              (lambda (_n parent &rest _)
                (save-excursion
                  (catch 'term
                    (while parent
                      (goto-char (treesit-node-start parent))
                      (when (looking-back (rx bol (* whitespace))
                                          (line-beginning-position))
                        (throw 'term (point)))
                      (setq parent (treesit-node-parent parent)))))))
        (cons 'prev-sibling (lambda (node parent bol &rest _)
                              (treesit-node-start
                               (or (treesit-node-prev-sibling node t)
                                   ;; If node is nil (indenting empty
                                   ;; line), we still try to guess the
                                   ;; previous sibling.
                                   (treesit-node-prev-sibling
                                    (treesit-node-first-child-for-pos
                                     parent bol)
                                    t)
                                   (treesit-node-child parent -1 t)))))
        (cons 'no-indent (lambda (_n _p bol &rest _) bol))
        (cons 'prev-line (lambda (_n _p bol &rest _)
                           (save-excursion
                             (goto-char bol)
                             (forward-line -1)
                             (skip-chars-forward " \t")
                             (point))))
        (cons 'column-0 (lambda (&rest _) (pos-bol)))
        ;; TODO: Document.
        (cons 'and (lambda (&rest fns)
                     (lambda (node parent bol &rest _)
                       (let (res)
                         (catch 'break
                           (dolist (fn fns)
                             (setq res (funcall fn node parent bol))
                             (unless res (throw 'break t))))
                         res))))
        (cons 'or (lambda (&rest fns)
                    (lambda (node parent bol &rest _)
                      (let (res)
                        (catch 'break
                          (dolist (fn fns)
                            (setq res (funcall fn node parent bol))
                            (and res (throw 'break t))))
                        res))))
        (cons 'not (lambda (fn)
                     (lambda (node parent bol &rest _)
                       (not (funcall fn node parent bol)))))
        (cons 'list (lambda (&rest fns)
                      (lambda (node parent bol &rest _)
                        (mapcar (lambda (fn)
                                  (funcall fn node parent bol))
                                fns)))))
  "A list of indent rule presets.
These presets can be used as MATCHER and ANCHOR values in
`treesit-simple-indent-rules'.  MATCHERs and ANCHORs are
functions that take 3 arguments: NODE, PARENT, and BOL.

MATCHER:

\(match NODE-TYPE PARENT-TYPE NODE-FIELD NODE-INDEX-MIN NODE-INDEX-MAX)

    NODE-TYPE checks for NODE's type, PARENT-TYPE checks for
    PARENT's type, NODE-FIELD checks for the field name of NODE
    in PARENT, NODE-INDEX-MIN and NODE-INDEX-MAX check for
    NODE's index in PARENT.  Therefore, to match the first child
    where PARENT is \"argument_list\", use

        (match nil \"argument_list\" nil 0 0).

    NODE-TYPE, PARENT-TYPE, and NODE-FIELD are regexps.
    NODE-TYPE can also be `null', which matches when NODE is nil.

no-node

    Matches the case where NODE is nil, i.e., there is no node
    that starts at point.  This is the case when indenting an
    empty line.

\(parent-is TYPE)

    Check that PARENT's type matches regexp TYPE.

\(node-is TYPE)

    Checks that NODE's type matches regexp TYPE.

\(field-is NAME)

    Checks that NODE's field name in PARENT matches regexp NAME.

\(n-p-gp NODE-TYPE PARENT-TYPE GRANDPARENT-TYPE)

    Checks for NODE's, its parent's, and its grandparent's type.

\(query QUERY)

    Queries PARENT with QUERY, and checks if NODE is
    captured (by any capture name).

comment-end

    Matches if text after point matches `comment-end-skip'.

catch-all

    Always matches.

ANCHOR:

first-sibling

    Returns the start of the first child of PARENT.

\(nth-sibling N &optional NAMED)

    Returns the start of the Nth child of PARENT.
    NAMED non-nil means count only named nodes.

parent

    Returns the start of PARENT.

grand-parent

    Returns the start of PARENT's parent.

great-grand-parent

    Returns the start of PARENT's parent's parent.

parent-bol

    Returns the beginning of non-space characters on the line where
    PARENT is on.

standalone-parent

    Finds the first ancestor node (parent, grandparent, etc.) that
    starts on its own line, and returns the start of that node.

prev-sibling

    Returns the start of NODE's previous sibling.

no-indent

    Returns the start of NODE.

prev-line

    Returns the first non-whitespace character on the previous line.

column-0

    Returns the beginning of the current line, which is at column 0.

comment-start

    Goes to the position that `comment-start-skip' would return,
    skips whitespace backwards, and returns the resulting
    position.  Assumes PARENT is a comment node.

prev-adaptive-prefix

    Goes to the beginning of previous non-empty line, and tries
    to match `adaptive-fill-regexp'.  If it matches, return the
    end of the match, otherwise return nil.  However, if the
    current line begins with a prefix, return the beginning of
    the prefix of the previous line instead, so that the two
    prefixes aligns.  This is useful for an `indent-relative'-like
    indent behavior for block comments.")

(defun treesit--simple-indent-eval (exp)
  "Evaluate EXP.

If EXP is an application and the function is a key in
`treesit-simple-indent-presets', use the corresponding value as
the function."
  ;; We don't want to match uncompiled lambdas, so make sure this cons
  ;; is not a function.  We could move the condition functionp
  ;; forward, but better be explicit.
  (cond ((and (consp exp) (not (functionp exp)))
         (apply (treesit--simple-indent-eval (car exp))
                (mapcar #'treesit--simple-indent-eval
                        (cdr exp))))
        ;; Presets override functions, so this condition comes before
        ;; `functionp'.
        ((alist-get exp treesit-simple-indent-presets))
        ((functionp exp) exp)
        ((symbolp exp)
         (if (null exp)
             exp
           ;; Matchers only return lambdas, anchors only return
           ;; integer, so we should never see a variable.
           (signal 'treesit-indent-error
                   (list "Couldn't find the preset corresponding to express=
ion"
                         exp))))
        (t exp)))

;; This variable might seem unnecessary: why split
;; `treesit-indent' and `treesit-simple-indent' into two
;; functions?  We add this variable in between because later we might
;; add more powerful indentation engines, and that new engine can
;; probably share `treesit-indent'.  It is also useful, suggested
;; by Stefan M, to have a function that figures out how much to indent
;; but doesn't actually performs the indentation, because we might
;; want to know where will a node indent to if we put it at some other
;; location, and use that information to calculate the actual
;; indentation.  And `treesit-simple-indent' is that function.  I
;; forgot the example Stefan gave, but it makes a lot of sense.
(defvar treesit-indent-function #'treesit-simple-indent
  "Function used by `treesit-indent' to do some of the work.

This function is called with

    (NODE PARENT BOL &rest _)

and returns

    (ANCHOR . OFFSET).

BOL is the position of the beginning of the line; NODE is the
\"largest\" node that starts at BOL (and isn't a root node);
PARENT is its parent; ANCHOR is a point (not a node), and OFFSET
is a number.  Emacs finds the column of ANCHOR and adds OFFSET to
it as the final indentation of the current line.")

(defun treesit--indent-largest-node-at (pos)
  "Get largest node that still starts at POS."
  (let* ((local-parsers (treesit-local-parsers-at pos nil t))
         (smallest-node
          (cond ((car local-parsers)
                 (let ((local-parser (caar local-parsers))
                       (host-parser (cdar local-parsers)))
                   (if (eq (treesit-node-start
                            (treesit-parser-root-node local-parser))
                           pos)
                       (treesit-node-at pos host-parser)
                     (treesit-node-at pos local-parser))))
                ((null (treesit-parser-list)) nil)
                ((eq 1 (length (treesit-parser-list nil nil t)))
                 (treesit-node-at pos))
                ((treesit-language-at pos)
                 (treesit-node-at pos (treesit-language-at pos)))
                (t (treesit-node-at pos))))
         (root (treesit-parser-root-node
                (treesit-node-parser smallest-node))))
    (treesit-parent-while
     smallest-node
     (lambda (node)
       (and (eq pos (treesit-node-start node))
            (not (treesit-node-eq node root)))))))

(defun treesit--indent-1 ()
  "Indent the current line.
Return (ANCHOR . OFFSET).  This function is used by
`treesit-indent' and `treesit-indent-region'."
  ;; Basically holds the common part between the two indent function.
  (let* ((bol (save-excursion
                (forward-line 0)
                (skip-chars-forward " \t")
                (point)))
         (node (treesit--indent-largest-node-at bol))
         (parser (if node
                     (treesit-node-parser node)
                   nil))
         ;; NODE would be nil if BOL is on a whitespace.  In that case
         ;; we set PARENT to the "node at point", which would
         ;; encompass the whitespace.
         (parent (cond ((and node parser)
                        (treesit-node-parent node))
                       (t (treesit-node-on bol bol)))))
    (funcall treesit-indent-function node parent bol)))

(defun treesit-indent ()
  "Indent according to the result of `treesit-indent-function'."
  (treesit-update-ranges (line-beginning-position)
                         (line-end-position))
  ;; We don't return 'noindent even if no rules match, because
  ;; `indent-for-tab-command' tries to indent itself when we return
  ;; 'noindent, which leads to wrong indentation at times.
  (pcase-let* ((`(,anchor . ,offset) (treesit--indent-1)))
    (when (and anchor offset)
      (let ((col (+ (save-excursion
                      (goto-char anchor)
                      (current-column))
                    offset))
            (delta (- (point-max) (point))))
        (indent-line-to col)
        ;; Now point is at the end of indentation.  If we started
        ;; from within the line, go back to where we started.
        (when (> (- (point-max) delta) (point))
          (goto-char (- (point-max) delta)))))))

;; Batch size can't be too large, because we put markers on each
;; ANCHOR, so a batch size of 400 lines means 400 markers.
(defvar treesit--indent-region-batch-size 400
  "How many lines of indent value do we precompute.
In `treesit-indent-region' we indent in batches: precompute
indent for each line, apply them in one go, let parser reparse,
and do it again.  This way the parser doesn't need to unnecessarily
reparse after indenting every single line.")

(defun treesit-indent-region (beg end)
  "Indent the region between BEG and END.
Similar to `treesit-indent', but indent a region instead."
  (treesit-update-ranges beg end)
  ;; We indent `treesit--indent-region-batch-size' lines at a time, to
  ;; reduce the number of times the parser needs to re-parse.  In each
  ;; batch, we go through each line and calculate the anchor and
  ;; offset as usual, but instead of modifying the buffer, we save
  ;; these information in a vector.  Once we've collected ANCHOR and
  ;; OFFSET for each line in the batch, we go through each line again
  ;; and apply the changes.  Now that buffer is modified, we need to
  ;; reparse the buffer before continuing to indent the next batch.
  (let* ((meta-len 2)
         (vector-len (* meta-len treesit--indent-region-batch-size))
         ;; This vector saves the indent meta for each line in the
         ;; batch.  It is a vector [ANCHOR OFFSET ANCHOR OFFSET...].
         ;; ANCHOR is a marker on the anchor position, and OFFSET is
         ;; an integer.  ANCHOR and OFFSET are either both nil, or
         ;; both valid.
         (meta-vec (make-vector vector-len 0))
         (lines-left-to-move 0)
         (end (copy-marker end t))
         (idx 0)
         (starting-pos 0)
         (announce-progress (> (- end beg) 80000)))
    (save-excursion
      (goto-char beg)
      ;; First pass.  Go through each line and compute the
      ;; indentation.
      (while (and (eq lines-left-to-move 0) (< (point) end))
        (setq idx 0
              starting-pos (point))
        (while (and (eq lines-left-to-move 0)
                    (< idx treesit--indent-region-batch-size)
                    (< (point) end))
          (if (looking-at (rx (* whitespace) eol) t)
              ;; Unlike in `indent-line' where we sometimes pre-indent
              ;; an empty line, We don't indent empty lines in
              ;; `indent-region'.  Set ANCHOR and OFFSET to nil.
              (setf (aref meta-vec (* idx meta-len)) nil
                    (aref meta-vec (+ 1 (* idx meta-len))) nil)
            (pcase-let* ((`(,anchor . ,offset) (treesit--indent-1))
                         (marker (aref meta-vec (* idx meta-len))))
              (if (not (and anchor offset))
                  ;; No indent for this line, either...
                  (if (markerp marker)
                      (progn
                        ;; ... Set marker and offset to do a dummy
                        ;; indent, or...
                        (back-to-indentation)
                        (move-marker marker (point))
                        (setf (aref meta-vec (+ 1 (* idx meta-len))) 0))
                    ;; ...Set anchor to nil so no indent is performed.
                    (setf (aref meta-vec (* idx meta-len)) nil))
                ;; Set ANCHOR.
                (if (markerp marker)
                    (move-marker marker anchor)
                  (setf (aref meta-vec (* idx meta-len))
                        (copy-marker anchor t)))
                ;; SET OFFSET.
                (setf (aref meta-vec (+ 1 (* idx meta-len))) offset))))
          (cl-incf idx)
          (setq lines-left-to-move (forward-line 1)))
        ;; Now IDX =3D last valid IDX + 1.
        (goto-char starting-pos)
        ;; Second pass, go to each line and apply the indentation.
        (dotimes (jdx idx)
          (let ((anchor (aref meta-vec (* jdx meta-len)))
                (offset (aref meta-vec (+ 1 (* jdx meta-len)))))
            (when (and anchor offset)
              (let ((col (save-excursion
                           (goto-char anchor)
                           (+ offset (current-column)))))
                (indent-line-to col))))
          (forward-line 1))
        (when announce-progress
          (message "Indenting region...%s%%"
                   (/ (* (- (point) beg) 100) (- end beg)))))
      ;; Delete markers.
      (dotimes (idx treesit--indent-region-batch-size)
        (let ((marker (aref meta-vec (* idx meta-len))))
          (when (markerp marker)
            (move-marker marker nil))))
      (move-marker end nil))))

(defun treesit-simple-indent (node parent bol)
  "Calculate indentation according to `treesit-simple-indent-rules'.

BOL is the position of the first non-whitespace character on the
current line.  NODE is the largest node that starts at BOL,
PARENT is NODE's parent.

Return (ANCHOR . OFFSET) where ANCHOR is a node, OFFSET is the
indentation offset, meaning indent to align with ANCHOR and add
OFFSET."
  (if (null parent)
      (progn (when treesit--indent-verbose
               (message "PARENT is nil, not indenting"))
             (cons nil nil))
    (let* ((language (treesit-node-language parent))
           (rules (alist-get language
                             treesit-simple-indent-rules)))
      (catch 'match
        (dolist (rule rules)
          (if (functionp rule)
              (let ((result (funcall rule node parent bol)))
                (when result
                  (when treesit--indent-verbose
                    (message "Matched rule: %S" rule))
                  (throw 'match result)))
            (let ((pred (nth 0 rule))
                  (anchor (nth 1 rule))
                  (offset (nth 2 rule)))
              ;; Found a match.
              (when (treesit--simple-indent-eval
                     (list pred node parent bol))
                (when treesit--indent-verbose
                  (message "Matched rule: %S" rule))
                (let ((anchor-pos
                       (treesit--simple-indent-eval
                        (list anchor node parent bol)))
                      (offset-val
                       (cond ((numberp offset) offset)
                             ((and (symbolp offset)
                                   (boundp offset))
                              (symbol-value offset))
                             (t (treesit--simple-indent-eval
                                 (list offset node parent bol))))))
                  (throw 'match (cons anchor-pos offset-val)))))))
        ;; Didn't find any match.
        (when treesit--indent-verbose
          (message "No matched rule"))
        (cons nil nil)))))

(defun treesit--read-major-mode ()
  "Read a major mode using completion.
Helper function to use in the `interactive' spec of `treesit-check-indent'."
  (let* ((default (and (symbolp major-mode) (symbol-name major-mode)))
	 (mode
	  (completing-read
	   (format-prompt "Target major mode" default)
	   obarray
	   (lambda (sym)
	     (and (string-suffix-p "-mode" (symbol-name sym))
		  (not (or (memq sym minor-mode-list)
                           (string-suffix-p "-minor-mode"
                                            (symbol-name sym))))))
	   nil nil nil default nil)))
    (cond
     ((equal mode "nil") nil)
     ((and (stringp mode) (fboundp (intern mode))) (intern mode))
     (t mode))))

(defun treesit-check-indent (mode)
  "Compare the current buffer with how major mode MODE would indent it."
  (interactive (list (treesit--read-major-mode)))
  (let ((source-buf (current-buffer)))
    (with-temp-buffer
      (insert-buffer-substring source-buf)
      (funcall mode)
      (indent-region (point-min) (point-max))
      (diff-buffers source-buf (current-buffer)))))

(defun treesit--indent-rules-optimize (rules)
  "Optimize simple indent RULES.
RULES should be a value suitable for
`treesit-simple-indent-rules'.  Return the optimized version of
RULES."
  ;; Right now this function just compiles queries.  It doesn't
  ;; byte-compile matchers and anchors because it doesn't make much
  ;; difference.
  (cl-loop for setting in rules
           for lang =3D (car setting)
           for indent-rules =3D (cdr setting)
           collect
           (cl-labels
               ;; Optimize a matcher or anchor.
               ((optimize-func (func)
                  (pcase func
                    (`(query ,qry)
                     (list 'query (treesit-query-compile lang qry)))
                    (`(and . ,fns)
                     (cons 'and (mapcar #'optimize-func fns)))
                    (`(or . ,fns)
                     (cons 'or (mapcar #'optimize-func fns)))
                    (_ func)))
                ;; Optimize a rule (MATCHER ANCHOR OFFSET).
                (optimize-rule (rule)
                  (if (functionp rule)
                      rule
                    (let ((matcher (nth 0 rule))
                          (anchor (nth 1 rule))
                          (offset (nth 2 rule)))
                      (list (optimize-func matcher)
                            (optimize-func anchor)
                            offset)))))
             (cons lang (mapcar #'optimize-rule indent-rules)))))

(defun treesit-modify-indent-rules (new-rules rules &optional how)
  "Modify RULES using NEW-RULES.
As default replace rules with the same anchor.
When HOW is :prepend NEW-RULES are prepend to RULES, when
:append NEW-RULES are appended to RULES, when :replace (the default)
NEW-RULES replace rule in RULES which the same anchor."
  (let ((n-lang (car (car new-rules)))
	(lang (car (car rules))))
    (when (not (eq n-lang lang))
      (error "The language must be the same"))
    (let* ((nr (cdr (car new-rules)))
           (r (cdr (car rules)))
           (tmp nil)
           (result
            (cond
             ((eq how :prepend)
	      (append nr r))
             ((eq how :append)
              (append r nr))
             ((or (eq how :replace) t)
              (nreverse
               (progn
                 (dolist (new-rule nr)
	           (dolist (rule r)
	             (if (equal (nth 0 new-rule) (nth 0 rule))
		         (push new-rule tmp)
		       (push rule tmp))))
                 tmp))))))
      (push lang result)
      `(,result))))

;;; Search

(defun treesit-search-forward-goto
    (node predicate &optional start backward all)
  "Search forward for a node and move to its end position.

Stop at the first node after NODE that matches PREDICATE.
PREDICATE can be either a regexp that matches against each node's
type case-insensitively, or a function that takes a node and
returns nil/non-nil for match/no match.

If a node matches, move to that node and return the node,
otherwise return nil.  If START is non-nil, stop at the
beginning rather than the end of a node.

This function guarantees that the matched node it returns makes
progress in terms of buffer position: the start/end position of
the returned node is always STRICTLY greater/less than that of
NODE.

BACKWARD and ALL are the same as in `treesit-search-forward'."
  (when-let* ((start-pos (if start
                             (treesit-node-start node)
                           (treesit-node-end node)))
              (current-pos start-pos))
    ;; When searching forward and stopping at beginnings, or search
    ;; backward stopping at ends, it is possible to "roll back" in
    ;; position.  Take three nodes N1, N2, N3 as an example, if we
    ;; start at N3, search for forward for beginning, and N1 matches,
    ;; we would stop at beg of N1, which is backwards!  So we skip N1
    ;; and keep going.
    ;;
    ;;   |<--------N1------->|
    ;;   |<--N2-->| |<--N3-->|
    (while (and node (if backward
                         (>=3D current-pos start-pos)
                       (<=3D current-pos start-pos)))
      (setq node (treesit-search-forward
                  node predicate backward all))
      (setq current-pos (if start
                            (treesit-node-start node)
                          (treesit-node-end node))))
    (cond
     ;; When there is a match and match made progress, go to the
     ;; result position.
     ((and node
           (if backward
               (< current-pos (point))
             (> current-pos (point))))
      (goto-char current-pos)))
    node))

(make-obsolete 'treesit-sexp-type-regexp
               "`treesit-sexp-type-regexp' will be removed soon, use `trees=
it-thing-settings' instead." "30.1")

(defvar-local treesit-sexp-type-regexp nil
  "A regexp that matches the node type of sexp nodes.

A sexp node is a node that is bigger than punctuation, and
delimits medium sized statements in the source code.  It is,
however, smaller in scope than sentences.  This is used by
`treesit-forward-sexp' and friends.")

;; Avoid interpreting the symbol `list' as a function.
(put 'list 'treesit-thing-symbol t)

(defun treesit--scan-error (pred arg)
  (when-let* ((parent (treesit-thing-at (point) pred t))
              (boundary (treesit-node-child parent (if (> arg 0) -1 0))))
    (signal 'scan-error (list (format-message "No more %S to move across" p=
red)
                              (treesit-node-start boundary)
                              (treesit-node-end boundary)))))

(defun treesit-forward-sexp (&optional arg)
  "Tree-sitter implementation for `forward-sexp-function'.

ARG is described in the docstring of `forward-sexp'.

If point is inside a text environment where tree-sitter is not
supported, go forward a sexp using `forward-sexp-default-function'.
If point is inside code, use tree-sitter functions with the
following behavior.  If there are no further sexps to move across,
signal `scan-error' like `forward-sexp' does.  If point is already
at top-level, return nil without moving point.

What constitutes as text and source code sexp is determined
by `text' and `sexp' in `treesit-thing-settings'.

There is an alternative implementation in `treesit-forward-sexp-list'
that uses `list' in `treesit-thing-settings' to move only
across lists, whereas uses `forward-sexp-default-function' to move
across atoms (such as symbols or words) inside the list."
  (interactive "^p")
  (let ((arg (or arg 1))
        (pred (or treesit-sexp-type-regexp 'sexp))
        (node-at-point
         (when (null treesit-sexp-type-regexp)
           (treesit-node-at (point) (treesit-language-at (point))))))
    (or (when (and node-at-point
                   ;; Make sure point is strictly inside node.
                   (< (treesit-node-start node-at-point)
                      (point)
                      (treesit-node-end node-at-point))
                   (treesit-node-match-p node-at-point 'text t))
          (forward-sexp-default-function arg)
          t)
        (if (> arg 0)
            (treesit-end-of-thing pred (abs arg) 'restricted)
          (treesit-beginning-of-thing pred (abs arg) 'restricted))
        ;; If we couldn't move, we should signal an error and report
        ;; the obstacle, like `forward-sexp' does.  If we couldn't
        ;; find a parent, we simply return nil without moving point,
        ;; then functions like `up-list' will signal "at top level".
        (treesit--scan-error pred arg))))

(defun treesit--forward-list-with-default (arg default-function)
  "Move forward across a list.
=46all back to DEFAULT-FUNCTION as long as it doesn't cross
the boundaries of the list.

ARG is described in the docstring of `forward-list'."
  (let* ((pred (or treesit-sexp-type-regexp 'list))
         (arg (or arg 1))
         (cnt arg)
         (inc (if (> arg 0) 1 -1)))
    (while (/=3D cnt 0)
      (let* ((default-pos
              (condition-case _
                  (save-excursion
                    (funcall default-function inc)
                    (point))
                (scan-error nil)))
             (parent (treesit-thing-at (point) pred t))
             (sibling (if (> arg 0)
                          (treesit-thing-next (point) pred)
                        (treesit-thing-prev (point) pred))))
        (when (and parent sibling
                   (not (treesit-node-enclosed-p sibling parent)))
          (setq sibling nil))
        ;; Use the default function only if it doesn't go
        ;; over the sibling and doesn't go out of the current group.
        (or (when (and default-pos
                       (or (null sibling)
                           (if (> arg 0)
                               (<=3D default-pos (treesit-node-start siblin=
g))
                             (>=3D default-pos (treesit-node-end sibling))))
                       (or (null parent)
                           (if (> arg 0)
                               (< default-pos (treesit-node-end parent))
                             (> default-pos (treesit-node-start parent)))))
              (goto-char default-pos))
            (when sibling
              (goto-char (if (> arg 0)
                             (treesit-node-end sibling)
                           (treesit-node-start sibling))))
            (treesit--scan-error pred arg)))
      (setq cnt (- cnt inc)))))

(defun treesit-forward-sexp-list (&optional arg)
  "Alternative tree-sitter implementation for `forward-sexp-function'.

Whereas `treesit-forward-sexp' moves across both lists and atoms
using `sexp' in `treesit-thing-settings', this function uses
`list' in `treesit-thing-settings' to move only across lists.
But to move across atoms (such as symbols or words) inside the list
it uses `forward-sexp-default-function' as long as it doesn't go
outside of the boundaries of the current list.

ARG is described in the docstring of `forward-sexp-function'."
  (interactive "^p")
  (treesit--forward-list-with-default arg 'forward-sexp-default-function))

(defun treesit-forward-list (&optional arg)
  "Move forward across a list.
What constitutes a list is determined by `list' in
`treesit-thing-settings' that usually defines
parentheses-like expressions.

Unlike `forward-sexp', this command moves only across a list,
but not across atoms (such as symbols or words) inside the list.

It uses `forward-list-default-function' as long as it doesn't go
outside of the boundaries of the current list.

ARG is described in the docstring of `forward-list-function'."
  (interactive "^p")
  (treesit--forward-list-with-default arg 'forward-list-default-function))

(defun treesit-down-list (&optional arg)
  "Move forward down one level of parentheses.
What constitutes a level of parentheses is determined by
`list' in `treesit-thing-settings' that usually defines
parentheses-like expressions.

This command is the tree-sitter variant of `down-list'
redefined by the variable `down-list-function'.

ARG is described in the docstring of `down-list'."
  (interactive "^p")
  (let* ((pred 'list)
         (arg (or arg 1))
         (cnt arg)
         (inc (if (> arg 0) 1 -1)))
    (while (/=3D cnt 0)
      (let* ((default-pos
              (condition-case _
                  (save-excursion
                    (down-list-default-function inc)
                    (point))
                (scan-error nil)))
             (sibling (if (> arg 0)
                          (treesit-thing-next (point) pred)
                        (treesit-thing-prev (point) pred)))
             (child (when sibling
                      (treesit-node-child sibling (if (> arg 0) 0 -1)))))
        (or (when (and default-pos
                       (or (null child)
                           (if (> arg 0)
                               (<=3D default-pos (treesit-node-start child))
                             (>=3D default-pos (treesit-node-end child)))))
              (goto-char default-pos))
            (when child
              (goto-char (if (> arg 0)
                             (treesit-node-end child)
                           (treesit-node-start child))))
            (treesit--scan-error pred arg)))
      (setq cnt (- cnt inc)))))

(defun treesit-up-list (&optional arg escape-strings no-syntax-crossing)
  "Move forward out of one level of parentheses.
What constitutes a level of parentheses is determined by
`list' in `treesit-thing-settings' that usually defines
parentheses-like expressions.

This command is the tree-sitter variant of `up-list'
redefined by the variable `up-list-function'.

ARG is described in the docstring of `up-list'."
  (interactive "^p")
  (let* ((pred 'list)
         (arg (or arg 1))
         (cnt arg)
         (inc (if (> arg 0) 1 -1)))
    (while (/=3D cnt 0)
      (let* ((default-pos
              (condition-case _
                  (save-excursion
                    (let ((forward-sexp-function nil))
                      (up-list-default-function
                       inc escape-strings no-syntax-crossing))
                    (point))
                (scan-error nil)
                (user-error nil)))
             (parent (treesit-thing-at (point) pred)))
        (while (and parent (eq (point) (if (> arg 0)
                                           (treesit-node-end parent)
                                         (treesit-node-start parent))))
          (setq parent (treesit-parent-until parent pred)))
        (or (when (and default-pos
                       (or (null parent)
                           (if (> arg 0)
                               (<=3D default-pos (treesit-node-end parent))
                             (>=3D default-pos (treesit-node-start parent))=
)))
              (goto-char default-pos))
            (when parent
              (goto-char (if (> arg 0)
                             (treesit-node-end parent)
                           (treesit-node-start parent))))
            (user-error "At top level")))
      (setq cnt (- cnt inc)))))

(defun treesit-transpose-sexps (&optional arg)
  "Tree-sitter `transpose-sexps' function.
ARG is the same as in `transpose-sexps'.

Locate the named node closest to POINT, and transpose that node with
its named sibling node ARG nodes away.

Return a pair of positions as described by
`transpose-sexps-function' for use in `transpose-subr' and
friends."
  (let* ((pred #'treesit-node-named)
         (arg (or arg 1))
         (cnt arg)
         (inc (if (> arg 0) 1 -1))
         (pos (point))
         first sibling)
    (while (and pos (/=3D cnt 0))
      (setq sibling (if (> arg 0)
                        (treesit-thing-next pos pred)
                      (treesit-thing-prev pos pred)))
      (unless first
        (setq first (if (> arg 0)
                        (treesit-node-start sibling)
                      (treesit-node-end sibling))))
      (setq pos (when sibling
                  (if (> arg 0)
                      (treesit-node-end sibling)
                    (treesit-node-start sibling))))
      (setq cnt (- cnt inc)))
    (or (and sibling (cons pos first))
        (transpose-sexps-default-function arg))))

;;; Navigation, defun, things
;;
;; Emacs lets you define "things" by a regexp that matches the type of
;; a node, and here are some functions that lets you find the "things"
;; at/around point, navigate backward/forward a "thing", etc.
;;
;; The most obvious "thing" is a defun, and there are thin wrappers
;; around thing functions for defun for convenience.
;;
;; We have more command-like functions like:
;; - treesit-beginning-of-thing/defun
;; - treesit-end-of-thing/defun
;; - treesit-thing/defun-at-point
;;
;; And more generic functions like:
;; - treesit-thing-prev/next
;; - treesit-thing-at
;; - treesit-top-level-thing
;; - treesit-navigate-thing
;;
;; There are also some defun-specific functions, like
;; treesit-defun-name, treesit-add-log-current-defun.
;;
;; TODO: Integration with thing-at-point: once our thing interface is
;; stable.
;;
;; TODO: Integration with hideshow: I tried and failed, we need
;; SomeOne that understands hideshow to look at it.

(defvar-local treesit-defun-type-regexp nil
  "A regexp that matches the node type of defun nodes.
=46or example, \"(function|class)_definition\".

Sometimes not all nodes matched by the regexp are valid defuns.
In that case, set this variable to a cons cell of the
form (REGEXP . PRED), where PRED is a function that takes a
node (the matched node) and returns t if node is valid, or nil
for invalid node.

This is used by `treesit-beginning-of-defun' and friends.")

(defvar-local treesit-defun-tactic 'nested
  "Determines how Emacs treats nested defuns.
If the value is `top-level', Emacs only moves across top-level
defuns, if the value is `nested', Emacs recognizes nested defuns.")

(defvar-local treesit-defun-skipper #'treesit-default-defun-skipper
  "A function called after tree-sitter navigation moved a step.

It is called with no arguments.  By default, this function tries
to move to the beginning of a line, either by moving to the empty
newline after a defun, or the beginning of a defun.

If the value is nil, no skipping is performed.")

(defvar-local treesit-defun-name-function nil
  "A function that is called with a node and returns its defun name or nil.
If the node is a defun node, return the defun name, e.g., the
name of a function.  If the node is not a defun node, or the
defun node doesn't have a name, or the node is nil, return nil.")

(defvar-local treesit-add-log-defun-delimiter "."
  "The delimiter used to connect several defun names.
This is used in `treesit-add-log-current-defun'.")

(defun treesit-thing-definition (thing language)
  "Return the predicate for THING if it's defined for LANGUAGE.
A thing is considered defined if it has an entry in
`treesit-thing-settings'.

If LANGUAGE is nil, return the first definition for THING in
`treesit-thing-settings'."
  (if language
      (car (alist-get thing (alist-get language
                                       treesit-thing-settings)))
    (car (alist-get thing (mapcan (lambda (entry)
                                    (copy-tree (cdr entry)))
                                  treesit-thing-settings)))))

(defalias 'treesit-thing-defined-p #'treesit-thing-definition
  "Return non-nil if THING is defined for LANGUAGE.

\(fn THING LANGUAGE)")

(defun treesit-beginning-of-thing (thing &optional arg tactic)
  "Like `beginning-of-defun', but generalized into things.

THING can be a thing defined in `treesit-thing-settings', which see,
or a predicate.  ARG is the same as in `beginning-of-defun'.

TACTIC determines how does this function move between things.  It
can be `nested', `top-level', `restricted', or nil.  `nested'
means normal nested navigation: try to move to siblings first,
and if there aren't enough siblings, move to the parent and its
siblings.  `top-level' means only consider top-level things, and
nested things are ignored.  `restricted' means movement is
restricted inside the thing that encloses POS (i.e., parent),
should there be one.  If omitted, TACTIC is considered to be
`nested'.

Return non-nil if successfully moved, nil otherwise."
  (pcase-let* ((arg (or arg 1))
               (dest (treesit-navigate-thing
                      (point) (- arg) 'beg thing tactic)))
    (when dest
      (goto-char dest))))

(defun treesit-end-of-thing (thing &optional arg tactic)
  "Like `end-of-defun', but generalized into things.

THING can be a thing defined in `treesit-thing-settings', which
see, or a predicate.  ARG is the same as in `end-of-defun'.

TACTIC determines how does this function move between things.  It
can be `nested', `top-level', `restricted', or nil.  `nested'
means normal nested navigation: try to move to siblings first,
and if there aren't enough siblings, move to the parent and its
siblings.  `top-level' means only consider top-level things, and
nested things are ignored.  `restricted' means movement is
restricted inside the thing that encloses POS (i.e., parent),
should there be one.  If omitted, TACTIC is considered to be
`nested'.

Return non-nil if successfully moved, nil otherwise."
  (pcase-let* ((arg (or arg 1))
               (dest (treesit-navigate-thing
                      (point) arg 'end thing tactic)))
    (when dest
      (goto-char dest))))

(defun treesit-beginning-of-defun (&optional arg)
  "Move backward to the beginning of a defun.

With argument ARG, do it that many times.  Negative ARG means
move forward to the ARGth following beginning of defun.

If search is successful, return t, otherwise return nil.

This is a tree-sitter equivalent of `beginning-of-defun'.
Behavior of this function depends on `treesit-defun-type-regexp'
and `treesit-defun-skipper'.  If `treesit-defun-type-regexp' is
not set, Emacs also looks for definition of defun in
`treesit-thing-settings'."
  (interactive "^p")
  (or (not (eq this-command 'treesit-beginning-of-defun))
      (eq last-command 'treesit-beginning-of-defun)
      (and transient-mark-mode mark-active)
      (push-mark))
  (let ((orig-point (point))
        (success nil)
        (pred (or treesit-defun-type-regexp 'defun)))
    (catch 'done
      (dotimes (_ 2)

        (when (treesit-beginning-of-thing pred arg treesit-defun-tactic)
          (when treesit-defun-skipper
            (funcall treesit-defun-skipper)
            (setq success t)))

        ;; If we end up at the same point, it means we went to the
        ;; next beg-of-defun, but defun skipper moved point back to
        ;; where we started, in this case we just move one step
        ;; further.
        (if (or (eq arg 0) (not (eq orig-point (point))))
            (throw 'done success)
          (setq arg (if (> arg 0) (1+ arg) (1- arg))))))))

(defun treesit-end-of-defun (&optional arg _)
  "Move forward to next end of defun.

With argument ARG, do it that many times.
Negative argument -N means move back to Nth preceding end of defun.

This is a tree-sitter equivalent of `end-of-defun'.  Behavior of
this function depends on `treesit-defun-type-regexp' and
`treesit-defun-skipper'.  If `treesit-defun-type-regexp' is not
set, Emacs also looks for definition of defun in
`treesit-thing-settings'."
  (interactive "^p\nd")
  (let ((orig-point (point))
        (pred (or treesit-defun-type-regexp 'defun)))
    (if (or (null arg) (=3D arg 0)) (setq arg 1))
    (or (not (eq this-command 'treesit-end-of-defun))
        (eq last-command 'treesit-end-of-defun)
        (and transient-mark-mode mark-active)
        (push-mark))
    (catch 'done
      (dotimes (_ 2) ; Not making progress is better than infloop.

        (when (treesit-end-of-thing pred arg treesit-defun-tactic)
          (when treesit-defun-skipper
            (funcall treesit-defun-skipper)))

        ;; If we end up at the same point, it means we went to the
        ;; prev end-of-defun, but defun skipper moved point back to
        ;; where we started, in this case we just move one step
        ;; further.
        (if (or (eq arg 0) (not (eq orig-point (point))))
            (throw 'done nil)
          (setq arg (if (> arg 0) (1+ arg) (1- arg))))))))

(make-obsolete 'treesit-text-type-regexp
               "`treesit-text-type-regexp' will be removed soon, use `trees=
it-thing-settings' instead." "30.1")

(defvar-local treesit-text-type-regexp "\\`comment\\'"
  "A regexp that matches the node type of textual nodes.

A textual node is a node that is not normal code, such as
comments and multiline string literals.  For example,
\"(line|block)_comment\" in the case of a comment, or
\"text_block\" in the case of a string.  This is used by
`prog-fill-reindent-defun' and friends.")

(make-obsolete 'treesit-sentence-type-regexp
               "`treesit-sentence-type-regexp' will be removed soon, use `t=
reesit-thing-settings' instead." "30.1")

(defvar-local treesit-sentence-type-regexp nil
  "A regexp that matches the node type of sentence nodes.

A sentence node is a node that is bigger than a sexp, and
delimits larger statements in the source code.  It is, however,
smaller in scope than defuns.  This is used by
`treesit-forward-sentence' and friends.")

(defun treesit-forward-sentence (&optional arg)
  "Tree-sitter `forward-sentence-function' implementation.

ARG is the same as in `forward-sentence'.

If point is inside a text environment, go forward a prose
sentence using `forward-sentence-default-function'.  If point is
inside code, go forward a source code sentence.

What constitutes as text and source code sentence is determined
by `text' and `sentence' in `treesit-thing-settings'."
  (if (treesit-node-match-p (treesit-node-at (point)) 'text t)
      (funcall #'forward-sentence-default-function arg)
    (funcall
     (if (> arg 0) #'treesit-end-of-thing #'treesit-beginning-of-thing)
     'sentence (abs arg))))

(defun treesit-default-defun-skipper ()
  "Skips spaces after navigating a defun.
This function tries to move to the beginning of a line, either by
moving to the empty newline after a defun, or to the beginning of
the current line if the beginning of the defun is indented."
  ;; Moving forward, point at the end of a line and not already on an
  ;; empty line: go to BOL of the next line (which hopefully is an
  ;; empty line).
  (cond ((and (looking-at (rx (* (or " " "\t")) "\n"))
              (not (bolp)))
         (forward-line 1))
        ;; Moving backward, but there are some whitespace (and only
        ;; whitespace) between point and BOL: go back to BOL.
        ((looking-back (rx bol (+ (or " " "\t")))
                       (line-beginning-position))
         (beginning-of-line))))

(defun treesit--thing-sibling (pos thing prev)
  "Return the next or previous THING at POS.

If PREV is non-nil, return the previous THING.  It's guaranteed
that returned previous sibling's end <=3D POS, and returned next
sibling's beginning >=3D POS.

Return nil if no THING can be found.  THING should be a thing
defined in `treesit-thing-settings', or a predicate as described
in `treesit-thing-settings'."
  (let* ((cursor (treesit-node-at pos))
         (pos-pred (if prev
                       (lambda (n) (<=3D (treesit-node-end n) pos))
                     (lambda (n) (>=3D (treesit-node-start n) pos))))
         (iter-pred (lambda (node)
                      (and (treesit-node-match-p node thing t)
                           (funcall pos-pred node))))
         (sibling nil))
    (when cursor
      ;; Find the node just before/after POS to start searching.
      (save-excursion
        (while (and cursor (not (funcall pos-pred cursor)))
          (setq cursor (treesit-search-forward-goto
                        cursor "" prev prev t))))
      ;; Keep searching until we run out of candidates or found a
      ;; return value.
      (while (and cursor
                  (funcall pos-pred cursor)
                  (null sibling))
        (setq sibling (treesit-node-top-level cursor iter-pred t))
        (setq cursor (treesit-search-forward cursor thing prev prev)))
      sibling)))

(defun treesit-thing-prev (pos thing)
  "Return the previous THING at POS.

The returned node, if non-nil, must be before POS, i.e., its end
<=3D POS.

THING should be a thing defined in `treesit-thing-settings', or a
predicate as described in `treesit-thing-settings'."
  (treesit--thing-sibling pos thing t))

(defun treesit-thing-next (pos thing)
  "Return the next THING at POS.

The returned node, if non-nil, must be after POS, i.e., its
start >=3D POS.

THING should be a thing defined in `treesit-thing-settings', or a
predicate as described in `treesit-thing-settings'."
  (treesit--thing-sibling pos thing nil))

(defun treesit-thing-at (pos thing &optional strict)
  "Return the smallest THING enclosing POS.

The returned node, if non-nil, must enclose POS, i.e., its start
<=3D POS, its end > POS.  If STRICT is non-nil, the returned node's
start must < POS rather than <=3D POS.

THING should be a thing defined in `treesit-thing-settings', or
it can be a predicate described in `treesit-thing-settings'."
  (let* ((cursor (treesit-node-at pos))
         (iter-pred (lambda (node)
                      (and (treesit-node-match-p node thing t)
                           (if strict
                               (< (treesit-node-start node) pos)
                             (<=3D (treesit-node-start node) pos))
                           (< pos (treesit-node-end node))))))
    (treesit-parent-until cursor iter-pred t)))

;; The basic idea for nested defun navigation is that we first try to
;; move across sibling defuns in the same level, if no more siblings
;; exist, we move to parents's beg/end, rinse and repeat.  We never
;; move into a defun, only outwards.
;;
;; Let me describe roughly what does this function do: there are four
;; possible operations: prev-beg, next-end, prev-end, next-beg, and
;; each of (prev-sibling next-sibling and parent) could exist or not
;; exist.  So there are 4 times 8 =3D 32 situations.
;;
;; I'll only describe the situation when we go backward (prev-beg &
;; prev-end), and consider only prev-sibling & parent. Deriving the
;; reverse situations is left as an exercise for the reader.
;;
;; prev-beg (easy case):
;; 1. prev-sibling or parent exists
;;    -> go the prev-sibling/parent's beg
;;
;; prev-end (tricky):
;; 1. prev-sibling exists
;;    -> If we are already at prev-sibling's end, we need to go one
;;       step further, either to prev-prev-sibling's end, or parent's
;;       prev-sibling's end, etc.
;; 2. prev-sibling is nil but parent exists
;;    -> Obviously we don't want to go to parent's end, instead, we
;;       want to go to parent's prev-sibling's end.  Again, we recurse
;;       in the function to do that.
(defun treesit-navigate-thing (pos arg side thing &optional tactic recursin=
g)
  "Navigate thing ARG steps from POS.

If ARG is positive, move forward that many steps, if negative,
move backward.  If SIDE is `beg', stop at the beginning of a
thing, if SIDE is `end', stop at the end.

This function doesn't actually move point, it just returns the
position it would move to.  If there aren't enough things to move
across, return nil.

THING can be a regexp, a predicate function, and more.  See
`treesit-thing-settings' for details.

TACTIC determines how does this function move between things.  It
can be `nested', `top-level', `restricted', or nil.  `nested'
means normal nested navigation: try to move to siblings first,
and if there aren't enough siblings, move to the parent and its
siblings.  `top-level' means only consider top-level things, and
nested things are ignored.  `restricted' means movement is
restricted inside the thing that encloses POS (i.e., parent),
should there be one.  If omitted, TACTIC is considered to be
`nested'.

RECURSING is an internal parameter, if non-nil, it means this
function is called recursively."
  (pcase-let*
      ((counter (abs arg))
       ;; Move POS to the beg/end of NODE.  If NODE is nil, terminate.
       ;; Return the position we moved to.
       (advance (lambda (node)
                  (let ((dest (pcase side
                                ('beg (treesit-node-start node))
                                ('end (treesit-node-end node)))))
                    (if (null dest)
                        (throw 'term nil)
                      dest)))))
    (catch 'term
      (while (> counter 0)
        (let ((prev (treesit-thing-prev pos thing))
              (next (treesit-thing-next pos thing))
              (parent (treesit-thing-at pos thing t)))
          (when (and parent prev
                     (not (treesit-node-enclosed-p prev parent)))
            (setq prev nil))
          (when (and parent next
                     (not (treesit-node-enclosed-p next parent)))
            (setq next nil))
          ;; When PARENT is nil, nested and top-level are the same, if
          ;; there is a PARENT, make PARENT to be the top-level parent
          ;; and pretend there is no nested PREV and NEXT.
          (when (and (eq tactic 'top-level)
                     parent)
            (setq parent (treesit-node-top-level parent thing t)
                  prev nil
                  next nil))
          ;; If TACTIC is `restricted', the implementation is simple.
          ;; In principle we don't go to parent's beg/end for
          ;; `restricted' tactic, but if the parent is a "leaf thing"
          ;; (doesn't have any child "thing" inside it), then we can
          ;; move to the beg/end of it (bug#68899).
          (if (eq tactic 'restricted)
              (setq pos (funcall
                         advance
                         (cond ((and (null next) (null prev)) parent)
                               ((> arg 0) next)
                               (t prev))))
            ;; For `nested', it's a bit more work:
            ;; Move...
            (if (> arg 0)
                ;; ...forward.
                (if (and (eq side 'beg)
                         ;; Should we skip the defun (recurse)?
                         (cond (next (and (not recursing) ; [1] (see below)
                                          (eq pos (funcall advance next))))
                               (parent t))) ; [2]
                    ;; Special case: go to next beg-of-defun, but point
                    ;; is already on beg-of-defun.  Set POS to the end
                    ;; of next-sib/parent defun, and run one more step.
                    ;; If there is a next-sib defun, we only need to
                    ;; recurse once, so we don't need to recurse if we
                    ;; are already recursing [1]. If there is no
                    ;; next-sib but a parent, keep stepping out
                    ;; (recursing) until we got out of the parents until
                    ;; (1) there is a next sibling defun, or (2) no more
                    ;; parents [2].
                    ;;
                    ;; If point on beg-of-defun but we are already
                    ;; recurring, that doesn't count as special case,
                    ;; because we have already made progress (by moving
                    ;; the end of next before recurring.)
                    (setq pos (or (treesit-navigate-thing
                                   (treesit-node-end (or next parent))
                                   1 'beg thing tactic t)
                                  (throw 'term nil)))
                  ;; Normal case.
                  (setq pos (funcall advance (or next parent))))
              ;; ...backward.
              (if (and (eq side 'end)
                       (cond (prev (and (not recursing)
                                        (eq pos (funcall advance prev))))
                             (parent t)))
                  ;; Special case: go to prev end-of-defun.
                  (setq pos (or (treesit-navigate-thing
                                 (treesit-node-start (or prev parent))
                                 -1 'end thing tactic t)
                                (throw 'term nil)))
                ;; Normal case.
                (setq pos (funcall advance (or prev parent))))))
          ;; A successful step! Decrement counter.
          (cl-decf counter))))
    ;; Counter equal to 0 means we successfully stepped ARG steps.
    (if (eq counter 0) pos nil)))

;; TODO: In corporate into thing-at-point.
(defun treesit-thing-at-point (thing tactic)
  "Return the THING at point, or nil if none is found.

THING can be a symbol, a regexp, a predicate function, and more;
see `treesit-thing-settings' for details.

Return the top-level THING if TACTIC is `top-level'; return the
smallest enclosing THING as POS if TACTIC is `nested'."

  (let ((node (treesit-thing-at (point) thing)))
    (if (eq tactic 'top-level)
        (treesit-node-top-level node thing t)
      node)))

(defun treesit-defun-at-point ()
  "Return the defun node at point, or nil if none is found.

Respects `treesit-defun-tactic': returns the top-level defun if it
is `top-level', otherwise return the immediate parent defun if it
is `nested'.

Return nil if `treesit-defun-type-regexp' isn't set and `defun'
isn't defined in `treesit-thing-settings'."
  (when (or treesit-defun-type-regexp
            (treesit-thing-defined-p
             'defun (treesit-language-at (point))))
    (treesit-thing-at-point
     (or treesit-defun-type-regexp 'defun) treesit-defun-tactic)))

(defun treesit-defun-name (node)
  "Return the defun name of NODE.

Return nil if there is no name, or if NODE is not a defun node,
or if NODE is nil.

If `treesit-defun-name-function' is nil, always return nil."
  (when treesit-defun-name-function
    (funcall treesit-defun-name-function node)))

(defun treesit-add-log-current-defun ()
  "Return the name of the defun at point.

Used for `add-log-current-defun-function'.

The delimiter between nested defun names is controlled by
`treesit-add-log-defun-delimiter'."
  (let ((node (treesit-defun-at-point))
        (name nil))
    (while node
      (when-let* ((new-name (treesit-defun-name node)))
        (if name
            (setq name (concat new-name
                               treesit-add-log-defun-delimiter
                               name))
          (setq name new-name)))
      (setq node (treesit-node-parent node)))
    name))

;;; Imenu

(defvar treesit-simple-imenu-settings nil
  "Settings that configure `treesit-simple-imenu'.

It should be a list of (CATEGORY REGEXP PRED NAME-FN).

CATEGORY is the name of a category, like \"Function\", \"Class\",
etc.  REGEXP should be a regexp matching the type of nodes that
belong to CATEGORY.  PRED should be either nil or a function
that takes a node an the argument.  It should return non-nil if
the node is a valid node for CATEGORY, or nil if not.

CATEGORY could also be nil.  In that case the entries matched by
REGEXP and PRED are not grouped under CATEGORY.

NAME-FN should be either nil or a function that takes a defun
node and returns the name of that defun node.  If NAME-FN is nil,
`treesit-defun-name' is used.

`treesit-major-mode-setup' automatically sets up Imenu if this
variable is non-nil.")

;; `treesit-simple-imenu-settings' doesn't support multiple languages,
;; and we need to add multi-lang support for Imenu.  One option is to
;; extend treesit-simple-imenu-settings to specify language, either by
;; making it optionally an alist (just like
;; `treesit-aggregated-simple-imenu-settings'), or add a fifth element
;; to each setting.  But either way makes borrowing Imenu settings from
;; other modes difficult: with the alist approach, you'd need to check
;; whether other mode uses a plain list or an alist; with the fifth
;; element approach, again, you need to check if each setting has the
;; fifth element, and add it if not.
;;
;; OTOH, with `treesit-aggregated-simple-imenu-settings', borrowing
;; Imenu settings is easy: if `treesit-aggregated-simple-imenu-settings'
;; is non-nil, copy everything over; if `treesit-simple-imenu-settings'
;; is non-nil, copy the settings and put them under a language symbol.
(defvar treesit-aggregated-simple-imenu-settings nil
  "Settings that configure `treesit-simple-imenu' for multi-language modes.

The value should be an alist of (LANG . SETTINGS), where LANG is a
language symbol, and SETTINGS has the same form as
`treesit-simple-imenu-settings'.

When both this variable and `treesit-simple-imenu-settings' are non-nil,
this variable takes priority.")

(defun treesit--simple-imenu-1 (node pred name-fn)
  "Given a sparse tree, create an Imenu index.

NODE is a node in the tree returned by
`treesit-induce-sparse-tree' (not a tree-sitter node, its car is
a tree-sitter node).  Walk that tree and return an Imenu index.

Return a list of entries where each ENTRY has the form:

ENTRY :=3D (NAME . MARKER)
       | (NAME . ((\" \" . MARKER)
                  ENTRY
                  ...)

PRED and NAME-FN are the same as described in
`treesit-simple-imenu-settings'.  NAME-FN computes NAME in an
ENTRY.  MARKER marks the start of each tree-sitter node."
  (let* ((ts-node (car node))
         (children (cdr node))
         (subtrees (mapcan (lambda (node)
                             (treesit--simple-imenu-1 node pred name-fn))
                           children))
         ;; The root of the tree could have a nil ts-node.
         (name (when ts-node
                 (or (if name-fn
                         (funcall name-fn ts-node)
                       (treesit-defun-name ts-node))
                     "Anonymous")))
         (marker (when ts-node
                   (set-marker (make-marker)
                               (treesit-node-start ts-node)))))
    (cond
     ;; The tree-sitter node in the root node of the tree returned by
     ;; `treesit-induce-sparse-tree' is often nil.
     ((null ts-node)
      subtrees)
     ;; This tree-sitter node is not a valid entry, skip it.
     ((and pred (not (funcall pred ts-node)))
      subtrees)
     ;; Non-leaf node, return a (list of) subgroup.
     (subtrees
      `((,name
         ,(cons " " marker)
         ,@subtrees)))
     ;; Leaf node, return a (list of) plain index entry.
     (t (list (cons name marker))))))

(defun treesit--imenu-merge-entries (entries)
  "Merge ENTRIES by category.

ENTRIES is a list of (CATEGORY . SUB-ENTRIES...).  Merge them so there's
no duplicate CATEGORY.  CATEGORY's are strings.  The merge is stable,
meaning the order of elements are kept."
  (let ((return-entries nil))
    (dolist (entry entries)
      (let* ((category (car entry))
             (sub-entries (cdr entry))
             (existing-entries
              (alist-get category return-entries nil nil #'equal)))
        (if (not existing-entries)
            (push entry return-entries)
          (setf (alist-get category return-entries nil nil #'equal)
                (append existing-entries sub-entries)))))
    (nreverse return-entries)))

(defun treesit--generate-simple-imenu (node settings)
  "Return an Imenu index for NODE with SETTINGS.

NODE usually should be a root node of a parser.  SETTINGS is described
by `treesit-simple-imenu-settings'."
  (mapcan (lambda (setting)
            (pcase-let ((`(,category ,regexp ,pred ,name-fn)
                         setting))
              (when-let* ((tree (treesit-induce-sparse-tree
                                 node regexp))
                          (index (treesit--simple-imenu-1
                                  tree pred name-fn)))
                (if category
                    (list (cons category index))
                  index))))
          settings))

(defun treesit-simple-imenu ()
  "Return an Imenu index for the current buffer."
  (if (not treesit-aggregated-simple-imenu-settings)
      (treesit--generate-simple-imenu
       (treesit-parser-root-node treesit-primary-parser)
       treesit-simple-imenu-settings)
    ;; Use `treesit-aggregated-simple-imenu-settings'.  Remove languages
    ;; that doesn't have any Imenu entries.
    (seq-filter
     #'cdr
     (mapcar
      (lambda (entry)
        (let* ((lang (car entry))
               (settings (cdr entry))
               (global-parser (car (treesit-parser-list nil lang)))
               (local-parsers
                (treesit-parser-list nil lang 'embedded)))
          (cons (treesit-language-display-name lang)
                ;; No one says you can't have both global and local
                ;; parsers for the same language.  E.g., Rust uses
                ;; local parsers for the same language to handle
                ;; macros.
                (treesit--imenu-merge-entries
                 (mapcan (lambda (parser)
                           (treesit--generate-simple-imenu
                            (treesit-parser-root-node parser) settings))
                         (cons global-parser local-parsers))))))
      treesit-aggregated-simple-imenu-settings))))

;;; Outline minor mode

(defvar-local treesit-outline-predicate nil
  "Predicate used to find outline headings in the syntax tree.
The predicate can be a function, a regexp matching node type,
and more; see docstring of `treesit-thing-settings'.
It matches the nodes located on lines with outline headings.
Intended to be set by a major mode.  When nil, the predicate
is constructed from the value of `treesit-simple-imenu-settings'
when a major mode sets it.")

(defun treesit-outline-predicate--from-imenu (node)
  ;; Return an outline searching predicate created from Imenu.
  ;; Return the value suitable to set `treesit-outline-predicate'.
  ;; Create this predicate from the value `treesit-simple-imenu-settings'
  ;; that major modes set to find Imenu entries.  The assumption here
  ;; is that the positions of Imenu entries most of the time coincide
  ;; with the lines of outline headings.  When this assumption fails,
  ;; you can directly set a proper value to `treesit-outline-predicate'.
  (seq-some
   (lambda (setting)
     (and (string-match-p (nth 1 setting) (treesit-node-type node))
          (or (null (nth 2 setting))
              (funcall (nth 2 setting) node))))
   treesit-simple-imenu-settings))

(defun treesit-outline-search (&optional bound move backward looking-at)
  "Search for the next outline heading in the syntax tree.
=46or BOUND, MOVE, BACKWARD, LOOKING-AT, see the descriptions in
`outline-search-function'."
  (if looking-at
      (when-let* ((node (or (treesit-thing-at (pos-eol) treesit-outline-pre=
dicate)
                            (treesit-thing-at (pos-bol) treesit-outline-pre=
dicate)))
                  (start (treesit-node-start node)))
        (eq (pos-bol) (save-excursion (goto-char start) (pos-bol))))

    (let* ((bob-pos
            ;; `treesit-navigate-thing' can't find a thing at bobp,
            ;; so use `looking-at' to match at bobp.
            (and (bobp) (treesit-outline-search bound move backward t) (poi=
nt)))
           (pos
            ;; When function wants to find the current outline, point
            ;; is at the beginning of the current line.  When it wants
            ;; to find the next outline, point is at the second column.
            (unless bob-pos
              (if (eq (point) (pos-bol))
                  (if (bobp) (point) (1- (point)))
                (pos-eol))))
           (found (or bob-pos
                      (treesit-navigate-thing pos (if backward -1 1) 'beg
                                              treesit-outline-predicate))))
      (if found
          (if (or (not bound) (if backward (>=3D found bound) (<=3D found b=
ound)))
              (progn
                (goto-char found)
                (goto-char (pos-bol))
                (set-match-data (list (point) (pos-eol)))
                t)
            (when move (goto-char bound))
            nil)
        (when move (goto-char (or bound (if backward (point-min) (point-max=
)))))
        nil))))

(defun treesit-outline-level ()
  "Return the depth of the current outline heading."
  (let* ((node (treesit-node-at (point) nil t))
         (level (if (treesit-node-match-p node treesit-outline-predicate)
                    1 0)))
    (while (setq node (treesit-parent-until node treesit-outline-predicate))
      (setq level (1+ level)))
    (if (zerop level) 1 level)))

;;; Show paren mode

(defun treesit-show-paren-data--categorize (pos &optional end-p)
  (let* ((pred 'list)
         (parent (when (treesit-thing-defined-p
                        pred (treesit-language-at pos))
                   (treesit-parent-until
                    (treesit-node-at (if end-p (1- pos) pos)) pred)))
         (first (when parent (treesit-node-child parent 0)))
         (first-start (when first (treesit-node-start first)))
         (first-end (when first (treesit-node-end first)))
         (last (when parent (treesit-node-child parent -1)))
         (last-start (when last (treesit-node-start last)))
         (last-end (when last (treesit-node-end last)))
         (dir (if show-paren-when-point-inside-paren
                  (cond
                   ((and first (<=3D first-start pos first-end)) 1)
                   ((and last (<=3D last-start pos last-end)) -1))
                (cond
                 ((and first (=3D first-start pos)) 1)
                 ((and last (=3D pos last-end)) -1)))))
    (cond
     ((eq dir 1) (list first-start first-end last-start last-end))
     ((eq dir -1) (list last-start last-end first-start first-end)))))

(defun treesit-show-paren-data ()
  "A function suitable for `show-paren-data-function' (which see)."
  (or (treesit-show-paren-data--categorize (point))
      (unless (bobp) (treesit-show-paren-data--categorize (point) t))
      (when show-paren-when-point-in-periphery
        (let* ((ind-pos (save-excursion (back-to-indentation) (point)))
               (eol-pos
                (save-excursion
                  (end-of-line) (skip-chars-backward " \t" ind-pos) (point)=
)))
          (cond
           ((<=3D (point) ind-pos)
            (or (treesit-show-paren-data--categorize ind-pos)
                (unless (bobp)
                  (treesit-show-paren-data--categorize (1- eol-pos)))))
           ((>=3D (point) eol-pos)
            (unless (bobp)
              (treesit-show-paren-data--categorize (1- eol-pos)))))))
      ;; Fall back for parens in e.g. 'for_statement'
      (show-paren--default)))

;;; Activating tree-sitter

(defun treesit-ready-p (language &optional quiet)
  "Check whether tree-sitter is ready to be used for MODE and LANGUAGE.

LANGUAGE is the language symbol to check for availability.
It can also be a list of language symbols.

If tree-sitter is not ready, emit a warning and return nil.  If
the user has chosen to activate tree-sitter for LANGUAGE and
tree-sitter is ready, return non-nil.  If QUIET is t, don't emit
a warning in either case; if quiet is `message', display a message
instead of emitting a warning."
  (let ((language-list (if (consp language)
                           language
                         (list language)))
        msg)
    ;; Check for each condition and set MSG.
    (catch 'term
      (when (not (treesit-available-p))
        (setq msg (if (fboundp 'treesit-node-p)
                      ;; Windows loads tree-sitter dynamically.
                      "tree-sitter library is not available or failed to lo=
ad"
                    "Emacs is not compiled with tree-sitter library"))
        (throw 'term nil))
      (when (> (position-bytes (max (point-min) (1- (point-max))))
               treesit-max-buffer-size)
        (setq msg "buffer larger than `treesit-max-buffer-size'")
        (throw 'term nil))
      (dolist (lang language-list)
        (pcase-let ((`(,available . ,err)
                     (treesit-language-available-p lang t)))
          (when (not available)
            (setq msg (format "language grammar for %s is unavailable (%s):=
 %s"
                              lang (nth 0 err)
                              (string-join
                               (mapcar (lambda (x) (format "%s" x))
                                       (cdr err))
                               " ")))
            (throw 'term nil)))))
    ;; Decide if all conditions met and whether emit a warning.
    (if (not msg)
        t
      (setq msg (concat "Cannot activate tree-sitter, because " msg))
      (pcase quiet
        ('nil (display-warning 'treesit msg))
        ('message (message "%s" msg)))
      nil)))

(defun treesit-major-mode-setup ()
  "Activate tree-sitter to power major-mode features.

If `treesit-font-lock-settings' is non-nil, set up fontification
and enable `font-lock-mode'.

If `treesit-simple-indent-rules' is non-nil, set up indentation.

If `treesit-defun-type-regexp' is non-nil or `defun' is defined
in `treesit-thing-settings', set up `beginning-of-defun-function'
and `end-of-defun-function'.

If `treesit-defun-name-function' is non-nil, set up
`add-log-current-defun'.

If `treesit-simple-imenu-settings' or
`treesit-aggregated-simple-imenu-settings' is non-nil, set up Imenu.

If either `treesit-outline-predicate' or `treesit-simple-imenu-settings'
are non-nil, and Outline minor mode settings don't already exist, setup
Outline minor mode.

If `sexp', `sentence' are defined in `treesit-thing-settings',
enable tree-sitter navigation commands for them.

Make sure necessary parsers are created for the current buffer
before calling this function."
  (unless treesit-primary-parser
    (setq treesit-primary-parser (treesit--guess-primary-parser)))
  ;; Font-lock.
  (when treesit-font-lock-settings
    ;; `font-lock-mode' wouldn't set up properly if
    ;; `font-lock-defaults' is nil, see `font-lock-specified-p'.
    (setq-local font-lock-defaults
                '( nil nil nil nil
                   (font-lock-fontify-syntactically-function
                    . treesit-font-lock-fontify-region)))
    (treesit-font-lock-recompute-features)
    (add-hook 'pre-redisplay-functions #'treesit--pre-redisplay 0 t)
    (when treesit-primary-parser
      (treesit-parser-add-notifier
       treesit-primary-parser #'treesit--font-lock-mark-ranges-to-fontify))
    (treesit-validate-font-lock-rules treesit-font-lock-settings))
  ;; Syntax
  (add-hook 'syntax-propertize-extend-region-functions
            #'treesit--pre-syntax-ppss 0 t)
  ;; Indent.
  (when treesit-simple-indent-rules
    (setq-local treesit-simple-indent-rules
                (treesit--indent-rules-optimize
                 treesit-simple-indent-rules)))
  ;; Enable indent if simple indent rules are set, or the major mode
  ;; sets a custom indent function.
  (when (or treesit-simple-indent-rules
            (and (not (eq treesit-indent-function #'treesit-simple-indent))
                 treesit-indent-function))
    (setq-local indent-line-function #'treesit-indent)
    (setq-local indent-region-function #'treesit-indent-region))
  ;; Navigation.
  (when (or treesit-defun-type-regexp
            (treesit-thing-defined-p 'defun nil))
    (keymap-set (current-local-map) "<remap> <beginning-of-defun>"
                #'treesit-beginning-of-defun)
    (keymap-set (current-local-map) "<remap> <end-of-defun>"
                #'treesit-end-of-defun)
    ;; `end-of-defun' will not work completely correctly in nested
    ;; defuns due to its implementation.  However, many lisp programs
    ;; use `beginning/end-of-defun', so we should still set
    ;; `beginning/end-of-defun-function' so they still mostly work.
    ;; This is also what `cc-mode' does: rebind user commands and set
    ;; the variables.  In future we should update `end-of-defun' to
    ;; work with nested defuns.
    (setq-local beginning-of-defun-function #'treesit-beginning-of-defun)
    (setq-local end-of-defun-function #'treesit-end-of-defun))
  ;; Defun name.
  (when treesit-defun-name-function
    (setq-local add-log-current-defun-function
                #'treesit-add-log-current-defun))

  (setq-local transpose-sexps-function #'treesit-transpose-sexps)

  (when (treesit-thing-defined-p 'sexp nil)
    (setq-local forward-sexp-function #'treesit-forward-sexp))

  (when (treesit-thing-defined-p 'list nil)
    (setq-local forward-sexp-function #'treesit-forward-sexp-list)
    (setq-local forward-list-function #'treesit-forward-list)
    (setq-local down-list-function #'treesit-down-list)
    (setq-local up-list-function #'treesit-up-list)
    (setq-local show-paren-data-function 'treesit-show-paren-data))

  (when (treesit-thing-defined-p 'sentence nil)
    (setq-local forward-sentence-function #'treesit-forward-sentence))

  ;; Imenu.
  (when (or treesit-aggregated-simple-imenu-settings
            treesit-simple-imenu-settings)
    (setq-local imenu-create-index-function
                #'treesit-simple-imenu))

  ;; Outline minor mode.
  (when (and (or treesit-outline-predicate treesit-simple-imenu-settings)
             (not (seq-some #'local-variable-p
                            '(outline-search-function
                              outline-regexp outline-level))))
    (unless treesit-outline-predicate
      (setq treesit-outline-predicate
            #'treesit-outline-predicate--from-imenu))
    (setq-local outline-search-function #'treesit-outline-search
                outline-level #'treesit-outline-level))

  ;; Remove existing local parsers.
  (dolist (ov (overlays-in (point-min) (point-max)))
    (when-let* ((parser (overlay-get ov 'treesit-parser)))
      (treesit-parser-delete parser)
      (delete-overlay ov))))

;;; Helpers

(defun treesit-node-named (node)
  "Return non-nil if NODE has the property `named'."
  (treesit-node-check node 'named))

;;; Debugging

(defvar-local treesit--inspect-name nil
  "Used by `treesit-inspect-mode' to show node name in mode-line.")

(defun treesit-inspect-node-at-point (&optional arg)
  "Show information of the node at point.
If called interactively, show in echo area, otherwise set
`treesit--inspect-name' (which will appear in the mode-line
if `treesit-inspect-mode' is enabled).  Uses the first parser
in `treesit-parser-list'."
  (interactive "p")
  ;; NODE-LIST contains all the node that starts at point.
  (let* ((node-list
          (cl-loop for node =3D (treesit-node-at (point))
                   then (treesit-node-parent node)
                   while node
                   if (eq (treesit-node-start node)
                          (point))
                   collect node))
         (largest-node (car (last node-list)))
         (parent (treesit-node-parent largest-node))
         ;; node-list-ascending contains all the node bottom-up, then
         ;; the parent.
         (node-list-ascending
          (if (null largest-node)
              ;; If there are no nodes that start at point, just show
              ;; the node at point and its parent.
              (list (treesit-node-at (point))
                    (treesit-node-parent
                     (treesit-node-at (point))))
            (append node-list (list parent))))
         (name ""))
    ;; We draw nodes like (parent field-name: (node)) recursively,
    ;; so it could be (node1 field-name: (node2 field-name: (node3))).
    (dolist (node node-list-ascending)
      (setq
       name
       (concat
        (if (treesit-node-field-name node)
            (format " %s: " (treesit-node-field-name node))
          " ")
        (if (treesit-node-check node 'named) "(" "\"")
        (propertize (or (treesit-node-type node) "N/A")
                    'face
                    (if (treesit-node-eq node largest-node)
                        'bold nil))
        name
        (if (treesit-node-check node 'named) ")" "\""))))
    ;; Escape the percent character for mode-line. (Bug#65540)
    (setq treesit--inspect-name (string-replace "%" "%%" name))
    (force-mode-line-update)
    (when arg
      (if node-list
          (message "%s" treesit--inspect-name)
        (message "No node at point")))))

(define-minor-mode treesit-inspect-mode
  "Minor mode that displays in the mode-line the node which starts at point.

When this mode is enabled, the mode-line displays

    PARENT FIELD-NAME: (NODE FIELD-NAME: (CHILD (...)))

where NODE, CHILD, etc, are nodes which begin at point.  PARENT
is the parent of NODE.  NODE is displayed in bold typeface.
=46IELD-NAMEs are field names of NODE and CHILD, etc (see Info
node `(elisp)Language Grammar', heading \"Field names\").

If no node starts at point, i.e., point is in the middle of a
node, then the mode line displays the earliest node that spans point,
and its immediate parent.

This minor mode doesn't create parsers on its own.  It uses the first
parser in `treesit-parser-list'."
  :lighter nil
  (if treesit-inspect-mode
      (progn
        (add-hook 'post-command-hook
                  #'treesit-inspect-node-at-point 0 t)
        (add-to-list 'mode-line-misc-info
                     '(:eval treesit--inspect-name)))
    (remove-hook 'post-command-hook
                 #'treesit-inspect-node-at-point t)
    (setq mode-line-misc-info
          (remove '(:eval treesit--inspect-name)
                  mode-line-misc-info))))

(defun treesit-query-validate (language query)
  "Check if QUERY is valid for LANGUAGE.
If QUERY is invalid, display the query in a popup buffer, jump
to the offending pattern and highlight the pattern."
  (cl-assert (or (consp query) (stringp query)))
  (let ((buf (get-buffer-create "*tree-sitter check query*")))
    (with-temp-buffer
      (treesit-parser-create language)
      (condition-case err
          (progn (treesit-query-capture language query)
                 (message "QUERY is valid"))
        (treesit-query-error
         (with-current-buffer buf
           (let* ((data (cdr err))
                  (message (nth 0 data))
                  (start (nth 1 data))
                  (inhibit-read-only t))
             (erase-buffer)
             (insert (if (stringp query)
                         query
                       (treesit-query-expand query)))
             (goto-char start)
             (search-forward " " nil t)
             (put-text-property start (point) 'face 'error)
             (message "%s" (buffer-substring start (point)))
             (goto-char (point-min))
             (insert (format "%s: %d\n" message start))
             (forward-char start)))
         (pop-to-buffer buf)
         (view-mode))))))

;;; Explorer

(defface treesit-explorer-anonymous-node
  (let ((display t)
        (atts '(:inherit shadow)))
    `((,display . ,atts)))
  "Face for anonymous nodes in tree-sitter explorer.")

(defface treesit-explorer-field-name
  (let ((display t)
        (atts nil))
    `((,display . ,atts)))
  "Face for field names in tree-sitter explorer.")

(defvar-local treesit--explorer-buffer nil
  "Buffer used to display the syntax tree.")

(defvar-local treesit--explorer-source-buffer nil
  "Source buffer corresponding to the playground buffer.")

(defvar-local treesit--explorer-parser nil
  "The language used in the playground.")

(defvar-local treesit--explorer-refresh-timer nil
  "Timer for refreshing the syntax tree buffer.")

(defvar-local treesit--explorer-highlight-overlay nil
  "Overlay used to highlight in syntax tree and source buffer.")

(defvar-local treesit--explorer-last-node nil
  "Last top-level node used to generate syntax tree.")

(defvar treesit-explore-mode)

(defun treesit--explorer--nodes-to-highlight (parser)
  "Return nodes for PARSER covered in region.
This function tries to return the largest node possible.  If the
region covers exactly one node, that node is returned (in a
list).  If the region covers more than one node, two nodes are
returned: the very first one in the region and the very last one
in the region."
  (let* ((beg (region-beginning))
         (end (region-end))
         (node (treesit-node-on beg end parser))
         (node (or (treesit-parent-while
                    node
                    (lambda (n)
                      (<=3D beg (treesit-node-start n)
                          (treesit-node-end n) end)))
                   node)))
    ;; If NODE is completely contained in the region, return NODE,
    ;; otherwise return its children that are in the region.
    (if (<=3D beg (treesit-node-start node)
            (treesit-node-end node) end)
        (list node)
      (list (treesit-node-at beg)
            (treesit-search-forward
             (treesit-node-at end)
             (lambda (n)
               (<=3D (treesit-node-end n) end))
             t t)))))

(defun treesit--explorer-refresh ()
  "Update the syntax tree buffer."
  (when (and treesit-explore-mode
             (buffer-live-p treesit--explorer-buffer))
    (let* ((root (treesit-node-on
                  (window-start) (window-end) treesit--explorer-parser))
           ;; Only highlight the current top-level construct.
           ;; Highlighting the whole buffer is slow and unnecessary.
           ;; But if the buffer is small (ie, used in playground
           ;; style), just highlight the whole buffer.
           (top-level (if (< (buffer-size) 4000)
                          root
                        (treesit-node-first-child-for-pos
                         root (if (eolp)
                                  (max (point-min) (1- (point)))
                                (point))
                         t)))
           ;; Only highlight node when region is active, if we
           ;; highlight node at point the syntax tree is too jumpy.
           (nodes-hl
            (when (region-active-p)
              (treesit--explorer--nodes-to-highlight
               treesit--explorer-parser)))
           ;; If we didn't edit the buffer nor change the top-level
           ;; node, don't redraw the whole syntax tree.
           (highlight-only (treesit-node-eq
                            top-level treesit--explorer-last-node))
           (source-buffer (current-buffer)))
      (setq-local treesit--explorer-last-node top-level)
      (with-current-buffer treesit--explorer-buffer
        (let ((inhibit-read-only t))
          (setq-local treesit--explorer-source-buffer source-buffer)
          ;; Redraw the syntax tree or just rehighlight the focused
          ;; node.
          (when (and top-level (not highlight-only))
            (erase-buffer)
            (treesit--explorer-draw-node top-level))
          (when-let* ((pos (treesit--explorer-highlight-node nodes-hl))
                      (window (get-buffer-window
                               treesit--explorer-buffer)))
            (if highlight-only
                (goto-char pos)
              ;; If HIGHLIGHT-ONLY is nil, we erased the buffer and
              ;; re-inserted text, scroll down from the very top until
              ;; we can see the highlighted node.
              (goto-char (point-min))
              (while (and (null (pos-visible-in-window-p pos window))
                          (=3D (forward-line 4) 0))
                (set-window-start window (point))))
            (set-window-point window pos)))))))

(defun treesit--explorer-post-command (&rest _)
  "Post-command function that runs in the source buffer."
  (when treesit-explore-mode
    (when treesit--explorer-highlight-overlay
      (delete-overlay treesit--explorer-highlight-overlay))
    (when treesit--explorer-refresh-timer
      (cancel-timer treesit--explorer-refresh-timer))
    (setq-local treesit--explorer-refresh-timer
                (run-with-timer 0.1 nil #'treesit--explorer-refresh))))

(defun treesit--explorer-jump (button)
  "Mark the original text corresponding to BUTTON."
  (when (and (derived-mode-p 'treesit--explorer-tree-mode)
             (buffer-live-p treesit--explorer-source-buffer))
    (with-current-buffer treesit--explorer-source-buffer
      (let ((start (button-get button 'node-start))
            (end (button-get button 'node-end)))
        (when treesit--explorer-highlight-overlay
          (delete-overlay treesit--explorer-highlight-overlay))
        (setq-local treesit--explorer-highlight-overlay
                    (make-overlay start end nil t nil))
        (overlay-put treesit--explorer-highlight-overlay
                     'face 'highlight)))))

(defun treesit--explorer-highlight-node (nodes)
  "Highlight nodes in NODES in the syntax tree buffer.
Return the start of the syntax tree text corresponding to NODE."
  (when treesit--explorer-highlight-overlay
    (delete-overlay treesit--explorer-highlight-overlay))
  (let ((start-node (car nodes))
        (end-node (car (last nodes)))
        start end)
    (when (and start-node end-node)
      (cl-loop for ov in (overlays-in (point-min) (point-max))
               while (or (null start) (null end))
               if (treesit-node-eq start-node
                                   (overlay-get ov 'treesit-node))
               do (setq start (overlay-start ov))
               if (treesit-node-eq end-node (overlay-get ov 'treesit-node))
               do (setq end (overlay-end ov)))
      (when (and start end)
        (setq-local treesit--explorer-highlight-overlay
                    (make-overlay start end))
        (overlay-put treesit--explorer-highlight-overlay
                     'face 'highlight)
        start))))

(defun treesit--explorer-draw-node (node)
  "Draw the syntax tree of NODE.

When this function is called, point should be at the position
where the node should start.  When this function returns, it
leaves point at the end of the last line of NODE."
  (let* ((type (treesit-node-type node))
         (field-name (treesit-node-field-name node))
         (children (treesit-node-children node))
         (named (treesit-node-check node 'named))
         ;; Column number of the start of the field-name, aka start of
         ;; the whole node.
         (before-field-column (current-column))
         ;; Column number after the field-name.
         after-field-column
         ;; Column number after the type.
         after-type-column
         ;; Are all children suitable for inline?
         (all-children-inline
          (eq 0 (apply #'+ (mapcar #'treesit-node-child-count children))))
         ;; If the child is the first child, we can inline, if the
         ;; previous child is suitable for inline, this child can
         ;; inline, if the previous child is not suitable for inline,
         ;; this child cannot inline.
         (can-inline t)
         ;; The beg and end of this node.
         beg end)
    (when treesit--explorer-highlight-overlay
      (delete-overlay treesit--explorer-highlight-overlay))

    (setq beg (point))
    ;; Draw field name.  If all children are suitable for inline, we
    ;; draw everything in one line, other wise draw field name and the
    ;; rest of the node in two lines.
    (when field-name
      (insert (propertize (concat field-name ": ")
                          'face 'treesit-explorer-field-name))
      (when (and children (not all-children-inline))
        (insert "\n")
        (indent-to-column (1+ before-field-column))))
    (setq after-field-column (current-column))

    ;; Draw type.
    (if named
        (progn
          (insert "(")
          (insert-text-button
           type 'action #'treesit--explorer-jump
           'follow-link t
           'node-start (treesit-node-start node)
           'node-end (treesit-node-end node)))
      (pcase type
        ("\n" (insert "\\n"))
        ("\t" (insert "\\t"))
        (" " (insert "SPC"))
        (_ (insert type))))
    (setq after-type-column (current-column))

    ;; Draw children.
    (dolist (child children)
      ;; If a child doesn't have children, it is suitable for inline.
      (let ((draw-inline (eq 0 (treesit-node-child-count child)))
            (children-indent (1+ after-field-column)))
        (while
            ;; This form returns t if it wants to run another
            ;; iteration, returns nil if it wants to stop.
            (if (and draw-inline can-inline)
                ;; Draw children on the same line.
                (let ((inline-beg (point)))
                  (insert " ")
                  (treesit--explorer-draw-node child)
                  ;; If we exceeds window width, draw on the next line.
                  (if (< (current-column) (window-width))
                      nil
                    (delete-region inline-beg (point))
                    (setq draw-inline nil
                          children-indent (1+ after-type-column))
                    t))
              ;; Draw children on the new line.
              (insert "\n")
              (indent-to-column children-indent)
              (treesit--explorer-draw-node child)
              nil))
        (setq can-inline draw-inline)))

    ;; Done drawing children, draw the ending paren.
    (when named (insert ")"))
    (setq end (point))

    ;; Associate the text with NODE, so we can later find a piece of
    ;; text by a node.
    (let ((ov (make-overlay beg end)))
      (overlay-put ov 'treesit-node node)
      (overlay-put ov 'evaporate t)
      (when (not named)
        (overlay-put ov 'face 'treesit-explorer-anonymous-node)))))

(defun treesit--explorer-kill-explorer-buffer ()
  "Kill the explorer buffer of this buffer."
  (when (buffer-live-p treesit--explorer-buffer)
    (kill-buffer treesit--explorer-buffer)))

(defun treesit--explorer-generate-parser-alist ()
  "Return an alist of (PARSER-NAME . PARSER) for relevant parsers.
Relevant parsers include all global parsers and local parsers that
covers point.  PARSER-NAME are unique."
  (let* ((local-parsers (treesit-parser-list nil nil 'embedded))
         (local-parsers-at-point
          (treesit-local-parsers-at (point)))
         res)
    (dolist (parser (treesit-parser-list nil nil t))
      ;; Exclude local parsers that doesn't cover point.
      (when (or (memq parser local-parsers-at-point)
                (not (memq parser local-parsers)))
        (push (cons (concat (format "%s" parser)
                            (if (treesit-parser-tag parser)
                                (format " tag=3D%s"
                                        (treesit-parser-tag
                                         parser))
                              "")
                            (if (memq parser
                                      local-parsers-at-point)
                                " (local)"
                              "")
                            (propertize (format " %s" (gensym))
                                        'invisible t))
                    parser)
              res)))
    (nreverse res)))

(defvar-keymap treesit--explorer-tree-mode-map
  :doc "Keymap for the treesit tree explorer.

Navigates from button to button."
  :parent special-mode-map
  "n" #'forward-button
  "p" #'backward-button
  "TAB" #'forward-button
  "<backtab>" #'backward-button)

(define-derived-mode treesit--explorer-tree-mode special-mode
  "TS Explorer"
  "Mode for displaying syntax trees for `treesit-explore-mode'."
  nil)

(defun treesit-explorer-switch-parser (parser)
  "Switch explorer to use PARSER."
  (interactive
   (list (let* ((parser-alist
                 (treesit--explorer-generate-parser-alist))
                (parser-name (if (=3D (length parser-alist) 1)
                                 (car parser-alist)
                               (completing-read
                                "Parser: " (mapcar #'car parser-alist)))))
           (alist-get parser-name parser-alist
                      nil nil #'equal))))
  (unless treesit-explore-mode
    (user-error "Not in `treesit-explore-mode'"))
  (setq-local treesit--explorer-parser parser)
  (display-buffer treesit--explorer-buffer
                  (cons nil '((inhibit-same-window . t))))
  (setq-local treesit--explorer-last-node nil)
  (treesit--explorer-refresh))

(define-minor-mode treesit-explore-mode
  "Enable exploring the current buffer's syntax tree.
Pops up a window showing the syntax tree of the source in the
current buffer in real time.  The corresponding node enclosing
the text in the active region is highlighted in the explorer
window."
  :lighter " TSexplore"
  (if treesit-explore-mode
      (progn
        ;; Create explorer buffer.
        (unless (buffer-live-p treesit--explorer-buffer)
          (setq-local treesit--explorer-buffer
                      (get-buffer-create
                       (format "*tree-sitter explorer for %s*"
                               (buffer-name))))
          (with-current-buffer treesit--explorer-buffer
            (treesit--explorer-tree-mode)))
        ;; Select parser.
        (call-interactively #'treesit-explorer-switch-parser)
        ;; Set up variables and hooks.
        (add-hook 'post-command-hook
                  #'treesit--explorer-post-command 0 t)
        (add-hook 'kill-buffer-hook
                  #'treesit--explorer-kill-explorer-buffer 0 t)
        ;; Tell `desktop-save' to not save explorer buffers.
        (when (boundp 'desktop-modes-not-to-save)
          (unless (memq 'treesit--explorer-tree-mode
                        desktop-modes-not-to-save)
            (push 'treesit--explorer-tree-mode
                  desktop-modes-not-to-save)))
        ;; Tell `desktop-save' to not save this minor mode
        ;; that might disrupt loading the desktop
        ;; with the prompt to select a parser.
        (when (boundp 'desktop-minor-mode-table)
          (unless (member '(treesit-explore-mode nil)
                          desktop-minor-mode-table)
            (push '(treesit-explore-mode nil)
                  desktop-minor-mode-table))))
    ;; Turn off explore mode.
    (remove-hook 'post-command-hook
                 #'treesit--explorer-post-command t)
    (remove-hook 'kill-buffer-hook
                 #'treesit--explorer-kill-explorer-buffer t)
    (treesit--explorer-kill-explorer-buffer)))

(defun treesit-explore ()
  "Show the explorer."
  (interactive)
  (if (and treesit-explore-mode
           (buffer-live-p treesit--explorer-buffer))
      (display-buffer treesit--explorer-buffer '(nil (inhibit-same-window .=
 t)))
    (treesit-explore-mode)))

;;; Install & build language grammar

(defvar treesit-language-source-alist nil
  "Configuration for downloading and installing tree-sitter language gramma=
rs.

The value should be an alist where each element has the form

    (LANG . (URL REVISION SOURCE-DIR CC C++ COMMIT))

Only LANG and URL are mandatory.  LANG is the language symbol.
URL is the URL of the grammar's Git repository or a directory
where the repository has been cloned.

REVISION is the Git tag or branch of the desired version, defaulting to
the latest default branch.  If COMMIT is non-nil, checkout this commit
hash after cloning the repo.  COMMIT has precedence over REVISION if
both are non-nil.

SOURCE-DIR is the relative subdirectory in the repository in which
the grammar's parser.c file resides, defaulting to \"src\".

CC and C++ are C and C++ compilers, defaulting to \"cc\" and
\"c++\", respectively.")

(defun treesit--install-language-grammar-build-recipe (lang)
  "Interactively produce a download/build recipe for LANG and return it.
See `treesit-language-source-alist' for details."
  (when (y-or-n-p (format "There is no recipe for %s, do you want to build =
it interactively?" lang))
    (cl-labels ((empty-string-to-nil (string)
                  (if (equal string "") nil string)))
      (list
       lang
       (let ((repo-default (format "https://github.com/tree-sitter/tree-sit=
ter-%s" lang)))
         (read-string
          "Enter the URL of the Git repository of the language grammar: "
          (and (treesit--check-repo-url repo-default) repo-default)))
       (empty-string-to-nil
        (read-string
         "Enter the tag or branch (default: default branch): "))
       (empty-string-to-nil
        (read-string
         "Enter the subdirectory in which the parser.c file resides (defaul=
t: \"src\"): "))
       (empty-string-to-nil
        (read-string
         "Enter the C compiler to use (default: auto-detect): "))
       (empty-string-to-nil
        (read-string
         "Enter the C++ compiler to use (default: auto-detect): "))))))

(defun treesit--check-repo-url (url)
  (defvar url-request-method)
  (let ((url-request-method "HEAD"))
    (let ((buffer (condition-case nil (url-retrieve-synchronously url t t)
                    (file-error nil))))
      (and buffer
           (eql
            (buffer-local-value 'url-http-response-status buffer)
            200)))))

(defvar treesit--install-language-grammar-out-dir-history nil
  "History for OUT-DIR for `treesit-install-language-grammar'.")

(defvar treesit--install-language-grammar-full-clone nil
  "If non-nil, do a full clone when cloning git repos.")

(defvar treesit--install-language-grammar-blobless nil
  "If non-nil, create a blobless clone when cloning git repos.")

;;;###autoload
(defun treesit-install-language-grammar (lang &optional out-dir)
  "Build and install the tree-sitter language grammar library for LANG.

Interactively, if `treesit-language-source-alist' doesn't already
have data for building the grammar for LANG, prompt for its
repository URL and the C/C++ compiler to use.  The recipe built
by the prompts are saved for the current session if the
installation is successful and the grammar is loadable.

This command requires Git, a C compiler and (sometimes) a C++ compiler,
and the linker to be installed and on PATH.  It also requires that the
recipe for LANG exists in `treesit-language-source-alist'.

See `exec-path' for the current path where Emacs looks for
executable programs, such as the C/C++ compiler and linker.

Interactively, prompt for the directory in which to install the
compiled grammar files.  Non-interactively, use OUT-DIR; if it's
nil, the grammar is installed to the standard location, the
\"tree-sitter\" directory under `user-emacs-directory'."
  (interactive (list (intern
                      (completing-read
                       "Language: "
                       (mapcar #'car treesit-language-source-alist)))
                     'interactive))
  (let* ((recipe
          (or (assoc lang treesit-language-source-alist)
              (if (eq out-dir 'interactive)
                  (treesit--install-language-grammar-build-recipe
                   lang)
                (signal 'treesit-error `("Cannot find recipe for this langu=
age" ,lang)))))
         (default-out-dir
          (or (car treesit--install-language-grammar-out-dir-history)
              (locate-user-emacs-file "tree-sitter")))
         (out-dir
          (if (eq out-dir 'interactive)
              (read-string
               (format "Install to (default: %s): "
                       default-out-dir)
               nil
               'treesit--install-language-grammar-out-dir-history
               default-out-dir)
            ;; When called non-interactively, OUT-DIR should
            ;; default to DEFAULT-OUT-DIR.
            (or out-dir default-out-dir))))
    (when recipe
      (condition-case err
          (progn
            (apply #'treesit--install-language-grammar-1
                   (cons out-dir recipe))

            ;; Check that the installed language grammar is loadable.
            (pcase-let ((`(,available . ,err)
                         (treesit-language-available-p lang t)))
              (if (not available)
                  (display-warning
                   'treesit
                   (format "The installed language grammar for %s cannot be=
 located or has problems (%s): %s"
                           lang (nth 0 err)
                           (string-join
                            (mapcar (lambda (x) (format "%s" x))
                                    (cdr err))
                            " ")))
                ;; If success, Save the recipe for the current session.
                (setf (alist-get lang treesit-language-source-alist)
                      (cdr recipe)))))
        (error
         (display-warning
          'treesit
          (format "Error encountered when installing language grammar: %s"
                  err)))))))

(defun treesit--language-git-revision (repo-dir)
  "Return the Git revision of the repo in REPO-DIR.

Return the output of \"git describe\". If anything goes wrong, return
nil."
  (with-temp-buffer
    (cond
     ((eq 0 (call-process "git" nil t nil "-C" repo-dir "describe" "--tags"=
))
      (string-trim (buffer-string)))
     ((eq 0 (progn (erase-buffer)
                   (call-process "git" nil t nil
                                 "-C" repo-dir "rev-parse" "HEAD")))
      (string-trim (buffer-string)))
     (t nil))))

(defun treesit--language-git-timestamp (repo-dir)
  "Return the commit date in REPO-DIR in UNIX epoch.

Return nil if failed to run command."
  (with-temp-buffer
    (if (eq 0 (call-process
               "git" nil t nil "-C" repo-dir "log" "-1" "--format=3D%ct"))
        (string-to-number (string-trim (buffer-string)))
      nil)))

(defun treesit--call-process-signal (&rest args)
  "Run `call-process' with ARGS.
If it returns anything but 0, signal an error.  Use the buffer
content as signal data, and erase buffer afterwards."
  (unless (eq 0 (apply #'call-process args))
    (signal 'treesit-error (list "Command:"
                                 (string-join (cons (car args)
                                                    (nthcdr 4 args))
                                              " ")
                                 "Error output:"
                                 (buffer-string)))
    (erase-buffer)))

(defun treesit--git-checkout-branch (repo-dir revision)
  "Checkout REVISION in a repo located in REPO-DIR."
  (treesit--call-process-signal
   "git" nil t nil "-C" repo-dir "checkout" revision))

(defun treesit--git-clone-repo (url revision workdir)
  "Clone repo pointed by URL at commit REVISION to WORKDIR.

REVISION may be nil, in which case the cloned repo will be at its
default branch.

Use shallow clone by default.  Do a full clone when
`treesit--install-language-grammar-full-clone' is t.  Do a blobless
clone if `treesit--install-language-grammar-blobless' is t."
  (message "Cloning repository")
  ;; git clone xxx --depth 1 --quiet [-b yyy] workdir
  (let ((args (list "git" nil t nil "clone" url "--quiet")))
    (when (not treesit--install-language-grammar-full-clone)
      (setq args (append args (list "--depth" "1"))))
    (when treesit--install-language-grammar-blobless
      (setq args (append args (list "--filter=3Dblob:none"))))
    (when revision
      (setq args (append args (list "-b" revision))))
    (setq args (append args (list workdir)))
    (apply #'treesit--call-process-signal args)))

(defun treesit--install-language-grammar-1
    (out-dir lang url &optional revision source-dir cc c++ commit)
  "Compile and install a tree-sitter language grammar library.

OUT-DIR is the directory to put the compiled library file.  If it
is nil, the \"tree-sitter\" directory under user's Emacs
configuration directory is used (and automatically created if it
does not exist).

=46or LANG, URL, REVISION, SOURCE-DIR, GRAMMAR-DIR, CC, C++, COMMIT, see
`treesit-language-source-alist'.

Return the git revision of the installed grammar.  The revision is
generated by \"git describe\".  It only works when
`treesit--install-language-grammar-full-clone' is t.

If anything goes wrong, this function signals an `treesit-error'."
  (let* ((default-directory (make-temp-file "treesit-workdir" t))
         (maybe-repo-dir (expand-file-name url))
         (url-is-dir (file-accessible-directory-p maybe-repo-dir))
         (workdir (if url-is-dir
                      maybe-repo-dir
                    (expand-file-name "repo")))
         version)
    (unwind-protect
        (with-temp-buffer
          (if url-is-dir
              (when revision
                (treesit--git-checkout-branch workdir revision))
            (treesit--git-clone-repo url revision workdir))
          (when commit
            (treesit--git-checkout-branch workdir commit))
          (setq version (treesit--language-git-revision workdir))
          (treesit--build-grammar workdir out-dir lang source-dir cc c++))
      ;; Remove workdir if it's not a repo owned by user and we
      ;; managed to create it in the first place.
      (when (and (not url-is-dir) (file-exists-p workdir))
        (delete-directory workdir t)))
    version))

(defun treesit--build-grammar (workdir out-dir lang source-dir cc c++)
  "Compile a tree-sitter language grammar library.

WORKDIR is the cloned repo's directory.  OUT-DIR is the directory to put
the compiled library file.  If it is nil, the \"tree-sitter\" directory
under user's Emacs configuration directory is used (and automatically
created if it does not exist).

=46or LANG, SOURCE-DIR, CC, C++, see `treesit-language-source-alist'.

If anything goes wrong, this function signals an `treesit-error'."
  (let* ((lang (symbol-name lang))
         (source-dir (expand-file-name (or source-dir "src") workdir))
         (cc (or cc (seq-find #'executable-find '("cc" "gcc" "c99"))
                 ;; If no C compiler found, just use cc and let
                 ;; `call-process' signal the error.
                 "cc"))
         (c++ (or c++ (seq-find #'executable-find '("c++" "g++"))
                  "c++"))
         (soext (or (car dynamic-library-suffixes)
                    (signal 'treesit-error '("Emacs cannot figure out the f=
ile extension for dynamic libraries for this system, because `dynamic-libra=
ry-suffixes' is nil"))))
         (out-dir (or (and out-dir (expand-file-name out-dir))
                      (locate-user-emacs-file "tree-sitter")))
         (lib-name (concat "libtree-sitter-" lang soext)))
    (with-temp-buffer
      ;; We need to go into the source directory because some
      ;; header files use relative path (#include "../xxx").
      ;; cd "${sourcedir}"
      (setq default-directory source-dir)
      (message "Compiling library")
      ;; cc -fPIC -c -I. parser.c
      (treesit--call-process-signal
       cc nil t nil "-fPIC" "-c" "-I." "parser.c")
      ;; cc -fPIC -c -I. scanner.c
      (when (file-exists-p "scanner.c")
        (treesit--call-process-signal
         cc nil t nil "-fPIC" "-c" "-I." "scanner.c"))
      ;; c++ -fPIC -I. -c scanner.cc
      (when (file-exists-p "scanner.cc")
        (treesit--call-process-signal
         c++ nil t nil "-fPIC" "-c" "-I." "scanner.cc"))
      ;; cc/c++ -fPIC -shared *.o -o "libtree-sitter-${lang}.${soext}"
      (apply #'treesit--call-process-signal
             (if (file-exists-p "scanner.cc") c++ cc)
             nil t nil
             (if (eq system-type 'cygwin)
                 `("-shared" "-Wl,-dynamicbase"
                   ,@(directory-files
                      default-directory nil
                      (rx bos (+ anychar) ".o" eos))
                   "-o" ,lib-name)
               `("-fPIC" "-shared"
                 ,@(directory-files
                    default-directory nil
                    (rx bos (+ anychar) ".o" eos))
                 "-o" ,lib-name)))
      ;; Copy out.
      (unless (file-exists-p out-dir)
        (make-directory out-dir t))
      (let* ((library-fname (expand-file-name lib-name out-dir))
             (old-fname (concat library-fname ".old")))
        ;; Rename the existing shared library, if any, then
        ;; install the new one, and try deleting the old one.
        ;; This is for Windows systems, where we cannot simply
        ;; overwrite a DLL that is being used.
        (if (file-exists-p library-fname)
            (rename-file library-fname old-fname t))
        (copy-file lib-name (file-name-as-directory out-dir) t t)
        ;; Ignore errors, in case the old version is still used.
        (ignore-errors (delete-file old-fname)))
      (message "Library installed to %s/%s" out-dir lib-name))))

;;; Shortdocs

(defun treesit--generate-shortdoc-examples ()
  "Generate examples for shortdoc."
  (with-temp-buffer
    (let (node parent)
      (insert "int c =3D 0;")
      (print (treesit-parser-create 'c))
      (print (treesit-parser-list))
      (goto-char (point-min))
      (print (setq node (treesit-node-at (point))))
      (print (setq parent (treesit-node-parent node)))
      (print (treesit-node-children parent))
      (print (treesit-node-next-sibling node))
      (print (treesit-node-child-by-field-name parent "declarator"))
      nil)))

(define-short-documentation-group treesit

  "Parsers"
  (treesit-parser-create
   :no-eval (treesit-parser-create 'c)
   :eg-result-string "#<treesit-parser for c>")
  (treesit-parser-delete
   :no-value (treesit-parser-delete parser))
  (treesit-parser-list
   :no-eval (treesit-parser-list)
   :eg-result-string "(#<treesit-parser for c>)")
  (treesit-parser-buffer
   :no-eval (treesit-parser-buffer parser)
   :eg-result-string "#<buffer xdisp.c>")
  (treesit-parser-language
   :no-eval (treesit-parser-language parser)
   :eg-result c)
  (treesit-parser-add-notifier)
  (treesit-parser-remove-notifier)
  (treesit-parser-notifiers
   :no-eval (treesit-parser-notifiers parser)
   :eg-result (function1 function2 function3))


  "Parser ranges"
  (treesit-parser-set-included-ranges
   :no-value (treesit-parser-set-included-ranges parser '((1 . 4) (5 . 8))))
  (treesit-parser-included-ranges
   :no-eval (treesit-parser-included-ranges parser)
   :eg-result ((1 . 4) (5 . 8)))
  (treesit-query-range
   :no-eval (treesit-query-range node '((script_element) @cap))
   :eg-result ((1 . 4) (5 . 8)))


  "Retrieving a node"
  (treesit-node-at
   :no-eval (treesit-node-at (point))
   :eg-result-string "#<treesit-node (identifier) in 179-180>")
  (treesit-node-on
   :no-eval (treesit-node-on 18 28)
   :eg-result-string "#<treesit-node (compound_statement) in 143-290>")
  (treesit-buffer-root-node
   :no-eval (treesit-buffer-root-node)
   :eg-result-string "#<treesit-node (translation_unit) in 1-4830>")
  (treesit-parser-root-node
   :no-eval (treesit-parser-root-node parser)
   :eg-result-string "#<treesit-node (translation_unit) in 1-4830>")


  "Retrieving a node from another node"
  (treesit-node-get
      :no-eval (treesit-node-get node '((parent 1) (sibling 1) (text)))
      :eg-result-string "#<treesit-node (declaration) in 1-11>")
  (treesit-node-parent
   :no-eval (treesit-node-parent node)
   :eg-result-string "#<treesit-node (declaration) in 1-11>")
  (treesit-node-child
   :no-eval (treesit-node-child node 0)
   :eg-result-string "#<treesit-node (primitive_type) in 1-4>")
  (treesit-node-children
   :no-eval (treesit-node-children node)
   :eg-result-string "(#<treesit-node (primitive_type) in 1-4> #<treesit-no=
de (init_declarator) in 5-10> #<treesit-node \";\" in 10-11>)")
  (treesit-node-next-sibling
   :no-eval (treesit-node-next-sibling node)
   :eg-result-string "#<treesit-node (init_declarator) in 5-10>")
  (treesit-node-prev-sibling
   :no-eval (treesit-node-prev-sibling node)
   :eg-result-string "#<treesit-node (primitive_type) in 1-4>")
  (treesit-node-child-by-field-name
   :no-eval (treesit-node-child-by-field-name node "declarator")
   :eg-result-string "#<treesit-node (init_declarator) in 5-10>")


  (treesit-node-first-child-for-pos
   :no-eval (treesit-node-first-child-for-pos node 1)
   :eg-result-string "#<treesit-node (primitive_type) in 1-4>")
  (treesit-node-descendant-for-range
   :no-eval (treesit-node-descendant-for-range node 2 3)
   :eg-result-string "#<treesit-node (primitive_type) in 1-4>")


  "Searching for node"
  (treesit-search-subtree
   :no-eval (treesit-search-subtree node "function_definition")
   :eg-result-string "#<treesit-node (function_definition) in 57-146>")
  (treesit-search-forward
   :no-eval (treesit-search-forward node "function_definition")
   :eg-result-string "#<treesit-node (function_definition) in 57-146>")
  (treesit-search-forward-goto
   :no-eval (treesit-search-forward-goto node "function_definition")
   :eg-result-string "#<treesit-node (function_definition) in 57-146>")
  (treesit-induce-sparse-tree
   :no-eval (treesit-induce-sparse-tree node "function_definition")
   :eg-result-string "(nil (#<treesit-node (function_definition) in 57-146>=
) (#<treesit-node (function_definition) in 259-296>) (#<treesit-node (funct=
ion_definition) in 303-659>))")
  (treesit-filter-child
   :no-eval (treesit-filter-child node (lambda (n) (equal (treesit-node-typ=
e) "identifier")))
   :eg-result-string "(#<treesit-node (identifier) in 195-196>)")
  (treesit-parent-until
   :no-eval (treesit-parent-until node (lambda (p) (eq (treesit-node-start =
p) (point))))
   :eg-result-string "#<treesit-node (declaration) in 1-11>")
  (treesit-parent-while
   :no-eval (treesit-parent-while node (lambda (p) (eq (treesit-node-start =
p) (point))))
   :eg-result-string "#<treesit-node (declaration) in 1-11>")
  (treesit-node-top-level
   :no-eval (treesit-node-top-level node)
   :eg-result-string "#<treesit-node (declaration) in 1-11>")


  "Retrieving node information"
  (treesit-node-text
   :no-eval (treesit-node-text node)
   :eg-result "int")
  (treesit-node-start
   :no-eval (treesit-node-start node)
   :eg-result 1)
  (treesit-node-end
   :no-eval (treesit-node-end node)
   :eg-result 10)
  (treesit-node-type
   :no-eval (treesit-node-type node)
   :eg-result "function_definition")
  (treesit-node-field-name
   :no-eval (treesit-node-field-name node)
   :eg-result "body")


  (treesit-node-parser
   :no-eval (treesit-node-parser node)
   :eg-result-string "#<treesit-parser for c>")
  (treesit-node-language
   :no-eval (treesit-node-language node)
   :eg-result c)
  (treesit-node-buffer
   :no-eval (treesit-node-buffer node)
   :eg-result-string "#<buffer xdisp.c>")


  (treesit-node-index
   :no-eval (treesit-node-index node)
   :eg-result 0)
  (treesit-node-string
   :no-eval (treesit-node-string node)
   :eg-result-string "(init_declarator declarator: (identifier) value: (num=
ber_literal))")
  (treesit-node-check
   :no-eval (treesit-node-check node 'named)
   :eg-result t)
  (treesit-node-enclosed-p
   :no-eval (treesit-node-enclosed-p node1 node2)
   :no-eval (treesit-node-enclosed-p node1 '(12 . 18)))

  (treesit-node-field-name-for-child
   :no-eval (treesit-node-field-name-for-child node)
   :eg-result "body")
  (treesit-node-child-count
   :no-eval (treesit-node-child-count node)
   :eg-result 3)


  "Pattern matching"
  (treesit-query-capture
   :no-eval (treesit-query-capture node '((identifier) @id "return" @ret))
   :eg-result-string "((id . #<treesit-node (identifier) in 195-196>) (ret =
=2E #<treesit-node "return" in 338-344>))")
  (treesit-query-compile
   :no-eval (treesit-query-compile 'c '((identifier) @id "return" @ret))
   :eg-result-string "#<treesit-compiled-query>")
  (treesit-query-language
   :no-eval (treesit-query-language compiled-query)
   :eg-result c)
  (treesit-query-expand
   :eval (treesit-query-expand '((identifier) @id "return" @ret)))
  (treesit-pattern-expand
   :eval (treesit-pattern-expand :anchor)
   :eval (treesit-pattern-expand '(identifier))
   :eval (treesit-pattern-expand :equal))

  "Tree-sitter things and navigation"
  (treesit-thing-defined-p
   :no-eval (treesit-thing-defined-p 'sexp)
   :eg-result nil)
  (treesit-thing-definition
   :no-eval (treesit-thing-defined 'sexp)
   :eg-result (not ,(rx (or "{" "}" "[" "]" "(" ")" ","))))
  (treesit-thing-at
   :no-eval (treesit-thing-at 3943)
   :eg-result-string "#<treesit-node (identifier) in 3941-3949>")
  (treesit-thing-next
   :no-eval (treesit-thing-next 3943 'sexp))
  (treesit-navigate-thing
   :no-eval (treesit-navigate-thing 3943 1 'beg 'sexp))
  (treesit-beginning-of-thing
   :no-eval (treesit-beginning-of-thing 'defun 1 'nested))

  "Parsing a string"
  (treesit-parse-string
   :no-eval (treesit-parse-string "int c =3D 0;" 'c)
   :eg-result-string "#<treesit-node (translation_unit) in 1-11>")
  (treesit-query-string
   :no-eval (treesit-query-string "int c =3D 0;" '((identifier) @id) 'c)
   :eg-result-string "((id . #<treesit-node (identifier) in 5-6>))")

  "Misc"
  (treesit-subtree-stat
   :no-eval (treesit-subtree-stat node)
   :eg-result (6 33 487))
  (treesit-language-abi-version
   :no-eval (treesit-language-abi-version 'c)
   :eg-result 14)
  (treesit-grammar-location
   :no-eval (treesit-language-abi-version 'c))
  (treesit-language-display-name
   :no-eval (treesit-language-display-name 'cpp)
   :eg-result "C++"))

(provide 'treesit)

;;; treesit.el ends here

--nextPart2576831.XAFRqVoOGU
Content-Disposition: attachment; filename="js.el"
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-emacs-lisp; charset="UTF-8"; name="js.el"

;;; js.el --- Major mode for editing JavaScript  -*- lexical-binding: t -*-

;; Copyright (C) 2008-2025 Free Software Foundation, Inc.

;; Author: Karl Landstrom <karl.landstrom@HIDDEN>
;;         Daniel Colascione <dancol@HIDDEN>
;; Maintainer: Daniel Colascione <dancol@HIDDEN>
;; Version: 9
;; Date: 2009-07-25
;; Keywords: languages, javascript

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.

;;; Tree-sitter language versions
;;
;; js-ts-mode is known to work with the following languages and version:
;; - tree-sitter-jsdoc: v0.23.2
;; - tree-sitter-javascript: v0.23.1-2-g108b2d4
;;
;; We try our best to make builtin modes work with latest grammar
;; versions, so a more recent grammar version has a good chance to work.
;; Send us a bug report if it doesn't.

;;; Commentary:

;; This is based on Karl Landstrom's barebones javascript-mode.  This
;; is much more robust and works with cc-mode's comment filling
;; (mostly).
;;
;; The main features of this JavaScript mode are syntactic
;; highlighting (enabled with `font-lock-mode' or
;; `global-font-lock-mode'), automatic indentation and filling of
;; comments, and C preprocessor fontification.
;;
;; General Remarks:
;;
;; XXX: This mode assumes that block comments are not nested inside block
;; XXX: comments
;;
;; Exported names start with "js-"; private names start with
;; "js--".

;;; Code:

(require 'cc-mode)
(eval-when-compile
  (require 'cc-langs)
  (require 'cc-fonts))
(require 'newcomment)
(require 'imenu)
(require 'json)
(require 'prog-mode)
(require 'treesit)
(require 'c-ts-common) ; For comment indent and filling.
(treesit-declare-unavailable-functions)

(eval-when-compile
  (require 'cl-lib)
  (require 'rx))

;;; Constants

(defconst js--name-start-re "[[:alpha:]_$]"
  "Regexp matching the start of a JavaScript identifier, without grouping.")

(defconst js--stmt-delim-chars "^;{}?:")

(defconst js--name-re (concat js--name-start-re
                              "\\(?:\\s_\\|\\sw\\)*")
  "Regexp matching a JavaScript identifier, without grouping.")

(defconst js--objfield-re (concat js--name-re ":")
  "Regexp matching the start of a JavaScript object field.")

(defconst js--dotted-name-re
  (concat js--name-re "\\(?:\\." js--name-re "\\)*")
  "Regexp matching a dot-separated sequence of JavaScript names.")

(defconst js--cpp-name-re js--name-re
  "Regexp matching a C preprocessor name.")

(defconst js--opt-cpp-start "^\\s-*#\\s-*\\([[:alnum:]]+\\)"
  "Regexp matching the prefix of a cpp directive.
This includes the directive name, or nil in languages without
preprocessor support.  The first submatch surrounds the directive
name.")

(defconst js--plain-method-re
  (concat "^\\s-*?\\(" js--dotted-name-re "\\)\\.prototype"
          "\\.\\(" js--name-re "\\)\\s-*?=3D\\s-*?\\(\\(?:async[ \t\n]+\\)f=
unction\\)\\_>")
  "Regexp matching an explicit JavaScript prototype \"method\" declaration.
Group 1 is a (possibly-dotted) class name, group 2 is a method name,
and group 3 is the `function' keyword.")

(defconst js--plain-class-re
  (concat "^\\s-*\\(" js--dotted-name-re "\\)\\.prototype"
          "\\s-*=3D\\s-*{")
  "Regexp matching a JavaScript explicit prototype \"class\" declaration.
An example of this is \"Class.prototype =3D { method1: ...}\".")

;; var NewClass =3D BaseClass.extend(
(defconst js--mp-class-decl-re
  (concat "^\\s-*var\\s-+"
          "\\(" js--name-re "\\)"
          "\\s-*=3D\\s-*"
          "\\(" js--dotted-name-re
          "\\)\\.extend\\(?:Final\\)?\\s-*(\\s-*{?\\s-*$"))

;; var NewClass =3D Class.create()
(defconst js--prototype-obsolete-class-decl-re
  (concat "^\\s-*\\(?:var\\s-+\\)?"
          "\\(" js--dotted-name-re "\\)"
          "\\s-*=3D\\s-*Class\\.create()"))

(defconst js--prototype-objextend-class-decl-re-1
  (concat "^\\s-*Object\\.extend\\s-*("
          "\\(" js--dotted-name-re "\\)"
          "\\s-*,\\s-*{"))

(defconst js--prototype-objextend-class-decl-re-2
  (concat "^\\s-*\\(?:var\\s-+\\)?"
          "\\(" js--dotted-name-re "\\)"
          "\\s-*=3D\\s-*Object\\.extend\\s-*("))

;; var NewClass =3D Class.create({
(defconst js--prototype-class-decl-re
  (concat "^\\s-*\\(?:var\\s-+\\)?"
          "\\(" js--name-re "\\)"
          "\\s-*=3D\\s-*Class\\.create\\s-*(\\s-*"
          "\\(?:\\(" js--dotted-name-re "\\)\\s-*,\\s-*\\)?{?"))

;; Parent class name(s) (yes, multiple inheritance in JavaScript) are
;; matched with dedicated font-lock matchers
(defconst js--dojo-class-decl-re
  (concat "^\\s-*dojo\\.declare\\s-*(\"\\(" js--dotted-name-re "\\)"))

(defconst js--extjs-class-decl-re-1
  (concat "^\\s-*Ext\\.extend\\s-*("
          "\\s-*\\(" js--dotted-name-re "\\)"
          "\\s-*,\\s-*\\(" js--dotted-name-re "\\)")
  "Regexp matching an ExtJS class declaration (style 1).")

(defconst js--extjs-class-decl-re-2
  (concat "^\\s-*\\(?:var\\s-+\\)?"
          "\\(" js--name-re "\\)"
          "\\s-*=3D\\s-*Ext\\.extend\\s-*(\\s-*"
          "\\(" js--dotted-name-re "\\)")
  "Regexp matching an ExtJS class declaration (style 2).")

(defconst js--mochikit-class-re
  (concat "^\\s-*MochiKit\\.Base\\.update\\s-*(\\s-*"
          "\\(" js--dotted-name-re "\\)")
  "Regexp matching a MochiKit class declaration.")

(defconst js--dummy-class-style
  '(:name "[Automatically Generated Class]"))

(defconst js--class-styles
  `((:name            "Plain"
     :class-decl      ,js--plain-class-re
     :prototype       t
     :contexts        (toplevel)
     :framework       javascript)

    (:name            "MochiKit"
     :class-decl      ,js--mochikit-class-re
     :prototype       t
     :contexts        (toplevel)
     :framework       mochikit)

    (:name            "Prototype (Obsolete)"
     :class-decl      ,js--prototype-obsolete-class-decl-re
     :contexts        (toplevel)
     :framework       prototype)

    (:name            "Prototype (Modern)"
     :class-decl      ,js--prototype-class-decl-re
     :contexts        (toplevel)
     :framework       prototype)

    (:name            "Prototype (Object.extend)"
     :class-decl      ,js--prototype-objextend-class-decl-re-1
     :prototype       t
     :contexts        (toplevel)
     :framework       prototype)

    (:name            "Prototype (Object.extend) 2"
     :class-decl      ,js--prototype-objextend-class-decl-re-2
     :prototype       t
     :contexts        (toplevel)
     :framework       prototype)

    (:name            "Dojo"
     :class-decl      ,js--dojo-class-decl-re
     :contexts        (toplevel)
     :framework       dojo)

    (:name            "ExtJS (style 1)"
     :class-decl      ,js--extjs-class-decl-re-1
     :prototype       t
     :contexts        (toplevel)
     :framework       extjs)

    (:name            "ExtJS (style 2)"
     :class-decl      ,js--extjs-class-decl-re-2
     :contexts        (toplevel)
     :framework       extjs)

    (:name            "Merrill Press"
     :class-decl      ,js--mp-class-decl-re
     :contexts        (toplevel)
     :framework       merrillpress))

  "List of JavaScript class definition styles.

A class definition style is a plist with the following keys:

:name is a human-readable name of the class type

:class-decl is a regular expression giving the start of the
class.  Its first group must match the name of its class.  If there
is a parent class, the second group should match, and it should be
the name of the class.

If :prototype is present and non-nil, the parser will merge
declarations for this constructs with others at the same lexical
level that have the same name.  Otherwise, multiple definitions
will create multiple top-level entries.  Don't use :prototype
unnecessarily: it has an associated cost in performance.

If :strip-prototype is present and non-nil, then if the class
name as matched contains.")

(defconst js--available-frameworks
  (cl-loop for style in js--class-styles
           for framework =3D (plist-get style :framework)
           unless (memq framework available-frameworks)
           collect framework into available-frameworks
           finally return available-frameworks)
  "List of available JavaScript frameworks symbols.")

(defconst js--function-heading-1-re
  (concat
   "^\\s-*function\\(?:\\s-\\|\\*\\)+\\(" js--name-re "\\)")
  "Regexp matching the start of a JavaScript function header.
Match group 1 is the name of the function.")

(defconst js--function-heading-2-re
  (concat
   "^\\s-*\\(" js--name-re "\\)\\s-*:\\s-*function\\_>")
  "Regexp matching the start of a function entry in an associative array.
Match group 1 is the name of the function.")

(defconst js--function-heading-3-re
  (concat
   "^\\s-*\\(?:var\\s-+\\)?\\(" js--dotted-name-re "\\)"
   "\\s-*=3D\\s-*function\\_>")
  "Regexp matching a line in the JavaScript form \"var MUMBLE =3D function\=
".
Match group 1 is MUMBLE.")

(defconst js--macro-decl-re
  (concat "^\\s-*#\\s-*define\\s-+\\(" js--cpp-name-re "\\)\\s-*(")
  "Regexp matching a CPP macro definition, up to the opening parenthesis.
Match group 1 is the name of the macro.")

(defun js--regexp-opt-symbol (list)
  "Like `regexp-opt', but surround the result with `\\\\_<' and `\\\\_>'."
  (concat "\\_<" (regexp-opt list t) "\\_>"))

(defconst js--keyword-re
  (js--regexp-opt-symbol
   '("abstract" "async" "await" "break" "case" "catch" "class" "const"
     "continue" "debugger" "default" "delete" "do" "else"
     "enum" "export" "extends" "final" "finally" "for"
     "function" "goto" "if" "implements" "import" "in"
     "instanceof" "interface" "native" "new" "of" "package"
     "private" "protected" "public" "return" "static"
     "super" "switch" "synchronized" "throw"
     "throws" "transient" "try" "typeof" "var" "void" "let"
     "yield" "volatile" "while" "with"))
  "Regexp matching any JavaScript keyword.")

(defconst js--basic-type-re
  (js--regexp-opt-symbol
   '("boolean" "byte" "char" "double" "float" "int" "long"
     "short" "void"))
  "Regular expression matching any predefined type in JavaScript.")

(defconst js--constant-re
  (js--regexp-opt-symbol '("false" "null" "undefined"
                                 "Infinity" "NaN"
                                 "true" "arguments" "this"))
  "Regular expression matching any future reserved words in JavaScript.")


(defconst js--font-lock-keywords-1
  (list
   "\\_<import\\_>"
   (list js--function-heading-1-re 1 'font-lock-function-name-face)
   (list js--function-heading-2-re 1 'font-lock-function-name-face))
  "Level one font lock keywords for `js-mode'.")

(defconst js--font-lock-keywords-2
  (append js--font-lock-keywords-1
          (list (list js--keyword-re 1 'font-lock-keyword-face)
                (cons js--basic-type-re 'font-lock-type-face)
                (cons js--constant-re 'font-lock-constant-face)))
  "Level two font lock keywords for `js-mode'.")

;; js--pitem is the basic building block of the lexical
;; database. When one refers to a real part of the buffer, the region
;; of text to which it refers is split into a conceptual header and
;; body. Consider the (very short) block described by a hypothetical
;; js--pitem:
;;
;;   function foo(a,b,c) { return 42; }
;;   ^                    ^            ^
;;   |                    |            |
;;   +- h-begin           +- h-end     +- b-end
;;
;; (Remember that these are buffer positions, and therefore point
;; between characters, not at them. An arrow drawn to a character
;; indicates the corresponding position is between that character and
;; the one immediately preceding it.)
;;
;; The header is the region of text [h-begin, h-end], and is
;; the text needed to unambiguously recognize the start of the
;; construct. If the entire header is not present, the construct is
;; not recognized at all. No other pitems may be nested inside the
;; header.
;;
;; The body is the region [h-end, b-end]. It may contain nested
;; js--pitem instances. The body of a pitem may be empty: in
;; that case, b-end is equal to header-end.
;;
;; The three points obey the following relationship:
;;
;;   h-begin < h-end <=3D b-end
;;
;; We put a text property in the buffer on the character *before*
;; h-end, and if we see it, on the character *before* b-end.
;;
;; The text property for h-end, js--pstate, is actually a list
;; of all js--pitem instances open after the marked character.
;;
;; The text property for b-end, js--pend, is simply the
;; js--pitem that ends after the marked character. (Because
;; pitems always end when the paren-depth drops below a critical
;; value, and because we can only drop one level per character, only
;; one pitem may end at a given character.)
;;
;; In the structure below, we only store h-begin and (sometimes)
;; b-end. We can trivially and quickly find h-end by going to h-begin
;; and searching for an js--pstate text property. Since no other
;; js--pitem instances can be nested inside the header of a
;; pitem, the location after the character with this text property
;; must be h-end.
;;
;; js--pitem instances are never modified (with the exception
;; of the b-end field). Instead, modified copies are added at
;; subsequence parse points.
;; (The exception for b-end and its caveats is described below.)
;;

(cl-defstruct (js--pitem (:type list))
  ;; IMPORTANT: Do not alter the position of fields within the list.
  ;; Various bits of code depend on their positions, particularly
  ;; anything that manipulates the list of children.

  ;; List of children inside this pitem's body
  (children nil :read-only t)

  ;; When we reach this paren depth after h-end, the pitem ends
  (paren-depth nil :read-only t)

  ;; Symbol or class-style plist if this is a class
  (type nil :read-only t)

  ;; See above
  (h-begin nil :read-only t)

  ;; List of strings giving the parts of the name of this pitem (e.g.,
  ;; '("MyClass" "myMethod"), or t if this pitem is anonymous
  (name nil :read-only t)

  ;; THIS FIELD IS MUTATED, and its value is shared by all copies of
  ;; this pitem: when we copy-and-modify pitem instances, we share
  ;; their tail structures, so all the copies actually have the same
  ;; terminating cons cell. We modify that shared cons cell directly.
  ;;
  ;; The field value is either a number (buffer location) or nil if
  ;; unknown.
  ;;
  ;; If the field's value is greater than `js--cache-end', the
  ;; value is stale and must be treated as if it were nil. Conversely,
  ;; if this field is nil, it is guaranteed that this pitem is open up
  ;; to at least `js--cache-end'. (This property is handy when
  ;; computing whether we're inside a given pitem.)
  ;;
  (b-end nil))

;; The pitem we start parsing with.
(defconst js--initial-pitem
  (make-js--pitem
   :paren-depth most-negative-fixnum
   :type 'toplevel))

;;; User Customization

(defgroup js nil
  "Customization variables for JavaScript mode."
  :tag "JavaScript"
  :group 'languages)

(defcustom js-indent-level 4
  "Number of spaces for each indentation step in `js-mode'."
  :type 'integer
  :safe 'integerp)

(defcustom js-expr-indent-offset 0
  "Number of additional spaces for indenting continued expressions.
The value must be no less than minus `js-indent-level'."
  :type 'integer
  :safe 'integerp)

(defcustom js-paren-indent-offset 0
  "Number of additional spaces for indenting expressions in parentheses.
The value must be no less than minus `js-indent-level'."
  :type 'integer
  :safe 'integerp
  :version "24.1")

(defcustom js-square-indent-offset 0
  "Number of additional spaces for indenting expressions in square braces.
The value must be no less than minus `js-indent-level'."
  :type 'integer
  :safe 'integerp
  :version "24.1")

(defcustom js-curly-indent-offset 0
  "Number of additional spaces for indenting expressions in curly braces.
The value must be no less than minus `js-indent-level'."
  :type 'integer
  :safe 'integerp
  :version "24.1")

(defcustom js-switch-indent-offset 0
  "Number of additional spaces for indenting the contents of a switch block.
The value must not be negative."
  :type 'integer
  :safe 'integerp
  :version "24.4")

(defcustom js-flat-functions nil
  "Treat nested functions as top-level functions in `js-mode'.
This applies to function movement, marking, and so on."
  :type 'boolean)

(defcustom js-indent-align-list-continuation t
  "Align continuation of non-empty ([{ lines in `js-mode'."
  :version "26.1"
  :type 'boolean
  :safe 'booleanp)

(defcustom js-comment-lineup-func #'c-lineup-C-comments
  "Lineup function for `cc-mode-style', for C comments in `js-mode'."
  :type 'function)

(defcustom js-enabled-frameworks js--available-frameworks
  "Frameworks recognized by `js-mode'.
To improve performance, you may turn off some frameworks you
seldom use, either globally or on a per-buffer basis."
  :type (cons 'set (mapcar (lambda (x)
                             (list 'const x))
                           js--available-frameworks)))

(defvar js-js-switch-tabs (and (memq system-type '(darwin)) t)
  "Whether `js-mode' should display tabs while selecting them.
This is useful only if the windowing system has a good mechanism
for preventing Firefox from stealing the keyboard focus.")
(make-obsolete-variable 'js-js-switch-tabs "MozRepl no longer exists" "28.1=
")

(defvar js-js-tmpdir (locate-user-emacs-file "js/js")
  "Temporary directory used by `js-mode' to communicate with Mozilla.
This directory must be readable and writable by both Mozilla and Emacs.")
(make-obsolete-variable 'js-js-tmpdir "MozRepl no longer exists" "28.1")

(defvar js-js-timeout 5
  "Reply timeout for executing commands in Mozilla via `js-mode'.
The value is given in seconds.  Increase this value if you are
getting timeout messages.")
(make-obsolete-variable 'js-js-timeout "MozRepl no longer exists" "28.1")

(defcustom js-indent-first-init nil
  "Non-nil means specially indent the first variable declaration's initiali=
zer.
Normally, the first declaration's initializer is unindented, and
subsequent declarations have their identifiers aligned with it:

  var o =3D {
      foo: 3
  };

  var o =3D {
      foo: 3
  },
      bar =3D 2;

If this option has the value t, indent the first declaration's
initializer by an additional level:

  var o =3D {
          foo: 3
      };

  var o =3D {
          foo: 3
      },
      bar =3D 2;

If this option has the value `dynamic', if there is only one declaration,
don't indent the first one's initializer; otherwise, indent it.

  var o =3D {
      foo: 3
  };

  var o =3D {
          foo: 3
      },
      bar =3D 2;"
  :version "25.1"
  :type '(choice (const nil) (const t) (const dynamic))
  :safe 'symbolp)

(defcustom js-chain-indent nil
  "Use \"chained\" indentation.
Chained indentation applies when the current line starts with \".\".
If the previous expression also contains a \".\" at the same level,
then the \".\"s will be lined up:

  let x =3D svg.mumble()
             .chained;"
  :version "26.1"
  :type 'boolean
  :safe 'booleanp)

(defcustom js-jsx-detect-syntax t
  "When non-nil, automatically detect whether JavaScript uses JSX.
`js-jsx-syntax' (which see) may be made buffer-local and set to
t.  The detection strategy can be customized by adding elements
to `js-jsx-regexps', which see."
  :version "27.1"
  :type 'boolean
  :safe 'booleanp)

(defcustom js-jsx-syntax nil
  "When non-nil, parse JavaScript with consideration for JSX syntax.

This enables proper font-locking and indentation of code using
=46acebook=E2=80=99s =E2=80=9CJSX=E2=80=9D syntax extension for JavaScript,=
 for use with
=46acebook=E2=80=99s =E2=80=9CReact=E2=80=9D library.  Font-locking is like=
 `sgml-mode'.
Indentation is also like `sgml-mode', although some indentation
behavior may differ slightly to align more closely with the
conventions of the React developer community.

When `js-mode' is already enabled, you should call
`js-jsx-enable' to set this variable.

It is set to be buffer-local (and t) when in `js-jsx-mode'."
  :version "27.1"
  :type 'boolean
  :safe 'booleanp)

(defcustom js-jsx-align->-with-< t
  "When non-nil, =E2=80=9C>=E2=80=9D will be indented to the opening =E2=80=
=9C<=E2=80=9D in JSX.

When this is enabled, JSX indentation looks like this:

  <element
    attr=3D\"\"
  >
  </element>
  <input
  />

When this is disabled, JSX indentation looks like this:

  <element
    attr=3D\"\"
    >
  </element>
  <input
    />"
  :version "27.1"
  :type 'boolean
  :safe 'booleanp)

(defcustom js-jsx-indent-level nil
  "When non-nil, indent JSX by this value, instead of like JS.

Let `js-indent-level' be 4.  When this variable is also set to
nil, JSX indentation looks like this (consistent):

  return (
      <element>
          <element>
              Hello World!
          </element>
      </element>
  )

Alternatively, when this variable is also set to 2, JSX
indentation looks like this (different):

  return (
      <element>
        <element>
          Hello World!
        </element>
      </element>
  )"
  :version "27.1"
  :type '(choice integer
                 (const :tag "Not Set" nil))
  :safe (lambda (x) (or (null x) (integerp x))))
;; This is how indentation behaved out-of-the-box until Emacs 27.  JSX
;; indentation was controlled with `sgml-basic-offset', which defaults
;; to 2, whereas `js-indent-level' defaults to 4.  Users who had the
;; same values configured for both their HTML and JS indentation would
;; luckily get consistent JSX indentation; most others were probably
;; unhappy.  I=E2=80=99d be surprised if anyone actually wants different
;; indentation levels, but just in case, here=E2=80=99s a way back to that.

(defcustom js-jsx-attribute-offset 0
  "Specifies a delta for JSXAttribute indentation.

Let `js-indent-level' be 2.  When this variable is also set to 0,
JSXAttribute indentation looks like this:

  <element
    attribute=3D\"value\">
  </element>

Alternatively, when this variable is also set to 2, JSXAttribute
indentation looks like this:

  <element
      attribute=3D\"value\">
  </element>

This variable is like `sgml-attribute-offset'."
  :version "27.1"
  :type 'integer
  :safe 'integerp)

;;; Syntax table and parsing

(defvar js-mode-syntax-table
  (let ((table (make-syntax-table)))
    (c-populate-syntax-table table)
    (modify-syntax-entry ?$ "_" table)
    (modify-syntax-entry ?` "\"" table)
    table)
  "Syntax table for `js-mode' and `js-ts-mode'.")

(defvar-local js--quick-match-re nil
  "Autogenerated regexp used by `js-mode' to match buffer constructs.")

(defvar-local js--quick-match-re-func nil
  "Autogenerated regexp used by `js-mode' to match constructs and functions=
=2E")

(defvar-local js--cache-end 1
  "Last valid buffer position for the `js-mode' function cache.")

(defvar-local js--last-parse-pos nil
  "Latest parse position reached by `js--ensure-cache'.")

(defvar-local js--state-at-last-parse-pos nil
  "Parse state at `js--last-parse-pos'.")

(defun js--maybe-join (prefix separator suffix &rest list)
  "Helper function for `js--update-quick-match-re'.
If LIST contains any element that is not nil, return its non-nil
elements, separated by SEPARATOR, prefixed by PREFIX, and ended
with SUFFIX as with `concat'.  Otherwise, if LIST is empty, return
nil.  If any element in LIST is itself a list, flatten that
element."
  (setq list (flatten-tree list))
  (when list
    (concat prefix (mapconcat #'identity list separator) suffix)))

(defun js--update-quick-match-re ()
  "Internal function used by `js-mode' for caching buffer constructs.
This updates `js--quick-match-re', based on the current set of
enabled frameworks."
  (setq js--quick-match-re
        (js--maybe-join
         "^[ \t]*\\(?:" "\\|" "\\)"

         ;; #define mumble
         "#define[ \t]+[a-zA-Z_]"

         (when (memq 'extjs js-enabled-frameworks)
           "Ext\\.extend")

         (when (memq 'prototype js-enabled-frameworks)
           "Object\\.extend")

          ;; var mumble =3D THING (
         (js--maybe-join
          "\\(?:var[ \t]+\\)?[a-zA-Z_$0-9.]+[ \t]*=3D[ \t]*\\(?:"
          "\\|"
          "\\)[ \t]*("

          (when (memq 'prototype js-enabled-frameworks)
                    "Class\\.create")

          (when (memq 'extjs js-enabled-frameworks)
            "Ext\\.extend")

          (when (memq 'merrillpress js-enabled-frameworks)
            "[a-zA-Z_$0-9]+\\.extend\\(?:Final\\)?"))

         (when (memq 'dojo js-enabled-frameworks)
           "dojo\\.declare[ \t]*(")

         (when (memq 'mochikit js-enabled-frameworks)
           "MochiKit\\.Base\\.update[ \t]*(")

         ;; mumble.prototypeTHING
         (js--maybe-join
          "[a-zA-Z_$0-9.]+\\.prototype\\(?:" "\\|" "\\)"

          (when (memq 'javascript js-enabled-frameworks)
            '( ;; foo.prototype.bar =3D function(
              "\\.[a-zA-Z_$0-9]+[ \t]*=3D[ \t]*function[ \t]*("

              ;; mumble.prototype =3D {
              "[ \t]*=3D[ \t]*{")))))

  (setq js--quick-match-re-func
        (concat "function\\|" js--quick-match-re)))

(defun js--forward-text-property (propname)
  "Move over the next value of PROPNAME in the buffer.
If found, return that value and leave point after the character
having that value; otherwise, return nil and leave point at EOB."
  (let ((next-value (get-text-property (point) propname)))
    (if next-value
        (forward-char)

      (goto-char (next-single-property-change
                  (point) propname nil (point-max)))
      (unless (eobp)
        (setq next-value (get-text-property (point) propname))
        (forward-char)))

    next-value))

(defun js--backward-text-property (propname)
  "Move over the previous value of PROPNAME in the buffer.
If found, return that value and leave point just before the
character that has that value, otherwise return nil and leave
point at BOB."
    (unless (bobp)
      (let ((prev-value (get-text-property (1- (point)) propname)))
        (if prev-value
            (backward-char)

          (goto-char (previous-single-property-change
                      (point) propname nil (point-min)))

          (unless (bobp)
            (backward-char)
            (setq prev-value (get-text-property (point) propname))))

        prev-value)))

(defsubst js--forward-pstate ()
  (js--forward-text-property 'js--pstate))

(defsubst js--backward-pstate ()
  (js--backward-text-property 'js--pstate))

(defun js--pitem-goto-h-end (pitem)
  (goto-char (js--pitem-h-begin pitem))
  (js--forward-pstate))

(defun js--re-search-forward-inner (regexp &optional bound count)
  "Helper function for `js--re-search-forward'."
  (let ((parse)
        str-terminator
        (orig-macro-end (save-excursion
                          (when (js--beginning-of-macro)
                            (c-end-of-macro)
                            (point)))))
    (while (> count 0)
      (re-search-forward regexp bound)
      (setq parse (syntax-ppss))
      (cond ((setq str-terminator (nth 3 parse))
             (when (eq str-terminator t)
               (setq str-terminator ?/))
             (re-search-forward
              (concat "\\([^\\]\\|^\\)" (string str-terminator))
              (line-end-position) t))
            ((nth 7 parse)
             (forward-line))
            ((or (nth 4 parse)
                 (and (eq (char-before) ?\/) (eq (char-after) ?\*)))
             (re-search-forward "\\*/"))
            ((and (not (and orig-macro-end
                            (<=3D (point) orig-macro-end)))
                  (js--beginning-of-macro))
             (c-end-of-macro))
            (t
             (setq count (1- count))))))
  (point))


(defun js--re-search-forward (regexp &optional bound noerror count)
  "Search forward, ignoring strings, cpp macros, and comments.
This function invokes `re-search-forward', but treats the buffer
as if strings, cpp macros, and comments have been removed.

If invoked while inside a macro, it treats the contents of the
macro as normal text."
  (unless count (setq count 1))
  (let ((saved-point (point))
        (search-fun
         (cond ((< count 0) (setq count (- count))
                #'js--re-search-backward-inner)
               ((> count 0) #'js--re-search-forward-inner)
               (t #'ignore))))
    (condition-case err
        (funcall search-fun regexp bound count)
      (search-failed
       (goto-char saved-point)
       (unless noerror
         (signal (car err) (cdr err)))))))


(defun js--re-search-backward-inner (regexp &optional bound count)
  "Auxiliary function for `js--re-search-backward'."
  (let ((parse)
        (orig-macro-start
         (save-excursion
           (and (js--beginning-of-macro)
                (point)))))
    (while (> count 0)
      (re-search-backward regexp bound)
      (when (and (> (point) (point-min))
                 (save-excursion (backward-char) (looking-at "/[/*]")))
        (forward-char))
      (setq parse (syntax-ppss))
      (cond ((nth 8 parse)
             (goto-char (nth 8 parse)))
            ((or (nth 4 parse)
                 (and (eq (char-before) ?/) (eq (char-after) ?*)))
             (re-search-backward "/\\*"))
            ((and (not (and orig-macro-start
                            (>=3D (point) orig-macro-start)))
                  (js--beginning-of-macro)))
            (t
             (setq count (1- count))))))
  (point))


(defun js--re-search-backward (regexp &optional bound noerror count)
  "Search backward, ignoring strings, preprocessor macros, and comments.

This function invokes `re-search-backward' but treats the buffer
as if strings, preprocessor macros, and comments have been
removed.

If invoked while inside a macro, treat the macro as normal text."
  (js--re-search-forward regexp bound noerror (if count (- count) -1)))

(defun js--forward-expression ()
  "Move forward over a whole JavaScript expression.
This function doesn't move over expressions continued across
lines."
  (cl-loop
   ;; non-continued case; simplistic, but good enough?
   do (cl-loop until (or (eolp)
                         (progn
                           (forward-comment most-positive-fixnum)
                           (memq (char-after) '(?\, ?\; ?\] ?\) ?\}))))
               do (forward-sexp))

   while (and (eq (char-after) ?\n)
              (save-excursion
                (forward-char)
                (js--continued-expression-p)))))

(defun js--forward-function-decl ()
  "Move forward over a JavaScript function declaration.
This puts point at the `function' keyword.

If this is a syntactically-correct non-expression function,
return the name of the function, or t if the name could not be
determined.  Otherwise, return nil."
  (unless (looking-at "\\(\\_<async\\_>[ \t\n]+\\)?\\_<function\\_>")
    (error "Invalid position"))
  (let ((name t))
    (goto-char (match-end 0))
    (forward-comment most-positive-fixnum)
    (when (eq (char-after) ?*)
      (forward-char)
      (forward-comment most-positive-fixnum))
    (when (looking-at js--name-re)
      (setq name (match-string-no-properties 0))
      (goto-char (match-end 0)))
    (forward-comment most-positive-fixnum)
    (and (eq (char-after) ?\( )
         (ignore-errors (forward-list) t)
         (progn (forward-comment most-positive-fixnum)
                (and (eq (char-after) ?{)
                     name)))))

(defun js--function-prologue-beginning (&optional pos)
  "Return the start of the JavaScript function prologue containing POS.
A function prologue is everything from start of the definition up
to and including the opening brace.  POS defaults to point.
If POS is not in a function prologue, return nil."
  (let (prologue-begin)
    (save-excursion
      (if pos
          (goto-char pos)
        (setq pos (point)))

      (when (save-excursion
              (forward-line 0)
              (or (looking-at js--function-heading-2-re)
                  (looking-at js--function-heading-3-re)))

        (setq prologue-begin (match-beginning 1))
        (when (<=3D prologue-begin pos)
          (goto-char (match-end 0))))

      (skip-syntax-backward "w_")
      (let ((start nil))
        (and (or (looking-at "\\_<function\\_>")
                 (js--re-search-backward "\\_<function\\_>" nil t))
             (progn
               (setq start (match-beginning 0))
               (goto-char start)
               (when (looking-back "\\_<async\\_>[ \t\n]+" (- (point) 30))
                 (setq start (match-beginning 0)))
               (js--forward-function-decl))
             (<=3D pos (point))
             (or prologue-begin start))))))

(defun js--beginning-of-defun-raw ()
  "Helper function for `js-beginning-of-defun'.
Go to previous defun-beginning and return the parse state for it,
or nil if we went all the way back to bob and don't find
anything."
  (js--ensure-cache)
  (let (pstate)
    (while (and (setq pstate (js--backward-pstate))
                (not (eq 'function (js--pitem-type (car pstate))))))
    (and (not (bobp)) pstate)))

(defun js--pstate-is-toplevel-defun (pstate)
  "Helper function for `js--beginning-of-defun-nested'.
If PSTATE represents a non-empty top-level defun, return the
top-most pitem.  Otherwise, return nil."
  (cl-loop for pitem in pstate
           with func-depth =3D 0
           with func-pitem
           if (eq 'function (js--pitem-type pitem))
           do (cl-incf func-depth)
           and do (setq func-pitem pitem)
           finally return (if (eq func-depth 1) func-pitem)))

(defun js--beginning-of-defun-nested ()
  "Helper function for `js--beginning-of-defun'.
Return the pitem of the function we went to the beginning of."
  (or
   ;; Look for the smallest function that encloses point...
   (cl-loop for pitem in (js--parse-state-at-point)
            if (and (eq 'function (js--pitem-type pitem))
                    (js--inside-pitem-p pitem))
            do (goto-char (js--pitem-h-begin pitem))
            and return pitem)

   ;; ...and if that isn't found, look for the previous top-level
   ;; defun
   (cl-loop for pstate =3D (js--backward-pstate)
            while pstate
            if (js--pstate-is-toplevel-defun pstate)
            do (goto-char (js--pitem-h-begin it))
            and return it)))

(defun js--beginning-of-defun-flat ()
  "Helper function for `js-beginning-of-defun'."
  (let ((pstate (js--beginning-of-defun-raw)))
    (when pstate
      (goto-char (js--pitem-h-begin (car pstate)))
      t)))

(defun js-beginning-of-defun (&optional arg)
  "Value of `beginning-of-defun-function' for `js-mode'."
  (setq arg (or arg 1))
  (let ((found))
    (while (and (not (eobp)) (< arg 0))
      (cl-incf arg)
      (when (and (not js-flat-functions)
                 (or (eq (js-syntactic-context) 'function)
                     (js--function-prologue-beginning)))
        (js-end-of-defun))

      (if (js--re-search-forward
           "\\_<function\\_>" nil t)
          (progn (goto-char (js--function-prologue-beginning))
                 (setq found t))
        (goto-char (point-max))
        (setq found nil)))

    (while (> arg 0)
      (cl-decf arg)
      ;; If we're just past the end of a function, the user probably wants
      ;; to go to the beginning of *that* function
      (when (eq (char-before) ?})
        (backward-char))

      (let ((prologue-begin (js--function-prologue-beginning)))
        (cond ((and prologue-begin (< prologue-begin (point)))
               (goto-char prologue-begin)
               (setq found t))

              (js-flat-functions
               (setq found (js--beginning-of-defun-flat)))
              (t
               (when (js--beginning-of-defun-nested)
                 (setq found t))))))
    found))

(defun js--flush-caches (&optional beg _ignored)
  "Flush the `js-mode' syntax cache after position BEG.
BEG defaults to `point-min', meaning to flush the entire cache."
  (interactive)
  (setq beg (or beg (save-restriction (widen) (point-min))))
  (setq js--cache-end (min js--cache-end beg)))

(defmacro js--debug (&rest _arguments)
  ;; `(message ,@arguments)
  )

(defun js--ensure-cache--pop-if-ended (open-items paren-depth)
  (let ((top-item (car open-items)))
    (when (<=3D paren-depth (js--pitem-paren-depth top-item))
      (cl-assert (not (get-text-property (1- (point)) 'js-pend)))
      (put-text-property (1- (point)) (point) 'js--pend top-item)
      (setf (js--pitem-b-end top-item) (point))
      (setq open-items
            ;; open-items must contain at least two items for this to
            ;; work, but because we push a dummy item to start with,
            ;; that assumption holds.
            (cons (js--pitem-add-child (cl-second open-items) top-item)
                  (cddr open-items)))))
  open-items)

(defmacro js--ensure-cache--update-parse ()
  "Helper function for `js--ensure-cache'.
Update parsing information up to point, referring to parse,
prev-parse-point, goal-point, and open-items bound lexically in
the body of `js--ensure-cache'."
  '(progn
     (setq goal-point (point))
     (goto-char prev-parse-point)
     (while (progn
              (setq open-items (js--ensure-cache--pop-if-ended
                                open-items (car parse)))
              ;; Make sure parse-partial-sexp doesn't stop because we *ente=
red*
              ;; the given depth -- i.e., make sure we're deeper than the t=
arget
              ;; depth.
              (cl-assert (> (nth 0 parse)
                            (js--pitem-paren-depth (car open-items))))
              (setq parse (parse-partial-sexp
                           prev-parse-point goal-point
                           (js--pitem-paren-depth (car open-items))
                           nil parse))

;;              (let ((overlay (make-overlay prev-parse-point (point))))
;;                (overlay-put overlay 'face '(:background "red"))
;;                (unwind-protect
;;                     (progn
;;                       (js--debug "parsed: %S" parse)
;;                       (sit-for 1))
;;                  (delete-overlay overlay)))

              (setq prev-parse-point (point))
              (< (point) goal-point)))

     (setq open-items (js--ensure-cache--pop-if-ended
                       open-items (car parse)))))

(defun js--show-cache-at-point ()
  (interactive)
  (require 'pp)
  (let ((prop (get-text-property (point) 'js--pstate)))
    (with-output-to-temp-buffer "*Help*"
      (pp prop))))

(defun js--split-name (string)
  "Split a JavaScript name into its dot-separated parts.
This also removes any prototype parts from the split name
\(unless the name is just \"prototype\" to start with)."
  (let ((name (save-match-data
                (split-string string "\\." t))))
    (unless (and (=3D (length name) 1)
                 (equal (car name) "prototype"))

      (setq name (remove "prototype" name)))))

(defvar js--guess-function-name-start nil)

(defun js--guess-function-name (position)
  "Guess the name of the JavaScript function at POSITION.
POSITION should be just after the end of the word \"function\".
Return the name of the function, or nil if the name could not be
guessed.

This function clobbers match data.  If we find the preamble
begins earlier than expected while guessing the function name,
set `js--guess-function-name-start' to that position; otherwise,
set that variable to nil."
  (setq js--guess-function-name-start nil)
  (save-excursion
    (goto-char position)
    (forward-line 0)
    (cond
     ((looking-at js--function-heading-3-re)
      (and (eq (match-end 0) position)
           (setq js--guess-function-name-start (match-beginning 1))
           (match-string-no-properties 1)))

     ((looking-at js--function-heading-2-re)
      (and (eq (match-end 0) position)
           (setq js--guess-function-name-start (match-beginning 1))
           (match-string-no-properties 1))))))

(defun js--clear-stale-cache ()
  ;; Clear any endings that occur after point
  (let (end-prop)
    (save-excursion
      (while (setq end-prop (js--forward-text-property
                             'js--pend))
        (setf (js--pitem-b-end end-prop) nil))))

  ;; Remove any cache properties after this point
  (remove-text-properties (point) (point-max)
                          '(js--pstate t js--pend t)))

(defun js--ensure-cache (&optional limit)
  "Ensures brace cache is valid up to the character before LIMIT.
LIMIT defaults to point."
  (setq limit (or limit (point)))
  (when (< js--cache-end limit)

    (c-save-buffer-state
        (open-items
         parse
         prev-parse-point
         name
         case-fold-search
         filtered-class-styles
         goal-point)

      ;; Figure out which class styles we need to look for
      (setq filtered-class-styles
            (cl-loop for style in js--class-styles
                     if (memq (plist-get style :framework)
                              js-enabled-frameworks)
                     collect style))

      (save-excursion
        (save-restriction
          (widen)

          ;; Find last known good position
          (goto-char js--cache-end)
          (unless (bobp)
            (setq open-items (get-text-property
                              (1- (point)) 'js--pstate))

            (unless open-items
              (goto-char (previous-single-property-change
                          (point) 'js--pstate nil (point-min)))

              (unless (bobp)
                (setq open-items (get-text-property (1- (point))
                                                    'js--pstate))
                (cl-assert open-items))))

          (unless open-items
            ;; Make a placeholder for the top-level definition
            (setq open-items (list js--initial-pitem)))

          (setq parse (syntax-ppss))
          (setq prev-parse-point (point))

          (js--clear-stale-cache)

          (narrow-to-region (point-min) limit)

          (cl-loop while (re-search-forward js--quick-match-re-func nil t)
                   for orig-match-start =3D (goto-char (match-beginning 0))
                   for orig-match-end =3D (match-end 0)
                   do (js--ensure-cache--update-parse)
                   for orig-depth =3D (nth 0 parse)

                   ;; Each of these conditions should return non-nil if
                   ;; we should add a new item and leave point at the end
                   ;; of the new item's header (h-end in the
                   ;; js--pitem diagram). This point is the one
                   ;; after the last character we need to unambiguously
                   ;; detect this construct. If one of these evaluates to
                   ;; nil, the location of the point is ignored.
                   if (cond
                       ;; In comment or string
                       ((nth 8 parse) nil)

                       ;; Regular function declaration
                       ((and (looking-at "\\_<function\\_>")
                             (setq name (js--forward-function-decl)))
                        (when (eq name t)
                          (setq name (js--guess-function-name orig-match-en=
d))
                          (if name
                              (when js--guess-function-name-start
                                (setq orig-match-start
                                      js--guess-function-name-start))

                            (setq name t)))

                        (cl-assert (eq (char-after) ?{))
                        (forward-char)
                        (save-excursion
                          (goto-char orig-match-start)
                          (when (looking-back "\\_<async\\_>[ \t\n]+"
                                              (- (point) 30))
                            (setq orig-match-start (match-beginning 0))))
                        (make-js--pitem
                         :paren-depth orig-depth
                         :h-begin orig-match-start
                         :type 'function
                         :name (if (eq name t)
                                   name
                                 (js--split-name name))))

                       ;; Macro
                       ((looking-at js--macro-decl-re)

                        ;; Macros often contain unbalanced parentheses.
                        ;; Make sure that h-end is at the textual end of
                        ;; the macro no matter what the parenthesis say.
                        (c-end-of-macro)
                        (js--ensure-cache--update-parse)

                        (make-js--pitem
                         :paren-depth (nth 0 parse)
                         :h-begin orig-match-start
                         :type 'macro
                         :name (list (match-string-no-properties 1))))

                       ;; "Prototype function" declaration
                       ((looking-at js--plain-method-re)
                        (goto-char (match-beginning 3))
                        (when (save-match-data
                                (js--forward-function-decl))
                          (forward-char)
                          (make-js--pitem
                           :paren-depth orig-depth
                           :h-begin orig-match-start
                           :type 'function
                           :name (nconc (js--split-name
                                         (match-string-no-properties 1))
                                        (list (match-string-no-properties 2=
))))))

                       ;; Class definition
                       ((cl-loop
                         with syntactic-context =3D
                         (js--syntactic-context-from-pstate open-items)
                         for class-style in filtered-class-styles
                         if (and (memq syntactic-context
                                       (plist-get class-style :contexts))
                                 (looking-at (plist-get class-style
                                                        :class-decl)))
                         do (goto-char (match-end 0))
                         and return
                         (make-js--pitem
                          :paren-depth orig-depth
                          :h-begin orig-match-start
                          :type class-style
                          :name (js--split-name
                                 (match-string-no-properties 1))))))

                   do (js--ensure-cache--update-parse)
                   and do (push it open-items)
                   and do (put-text-property
                           (1- (point)) (point) 'js--pstate open-items)
                   else do (goto-char orig-match-end))

          (goto-char limit)
          (js--ensure-cache--update-parse)
          (setq js--cache-end limit)
          (setq js--last-parse-pos limit)
          (setq js--state-at-last-parse-pos open-items)
          )))))

(defun js--end-of-defun-flat ()
  "Helper function for `js-end-of-defun'."
  (cl-loop while (js--re-search-forward "}" nil t)
           do (js--ensure-cache)
           if (get-text-property (1- (point)) 'js--pend)
           if (eq 'function (js--pitem-type it))
           return t
           finally do (goto-char (point-max))))

(defun js--end-of-defun-nested ()
  "Helper function for `js-end-of-defun'."
  (let* (pitem
         (this-end (save-excursion
                     (and (setq pitem (js--beginning-of-defun-nested))
                          (js--pitem-goto-h-end pitem)
                          (progn (backward-char)
                                 (forward-list)
                                 (point)))))
         found)

    (if (and this-end (< (point) this-end))
        ;; We're already inside a function; just go to its end.
        (goto-char this-end)

      ;; Otherwise, go to the end of the next function...
      (while (and (js--re-search-forward "\\_<function\\_>" nil t)
                  (not (setq found (progn
                                     (goto-char (match-beginning 0))
                                     (js--forward-function-decl))))))

      (if found (forward-list)
        ;; ... or eob.
        (goto-char (point-max))))))

(defun js-end-of-defun (&optional arg)
  "Value of `end-of-defun-function' for `js-mode'."
  (setq arg (or arg 1))
  (while (and (not (bobp)) (< arg 0))
    (cl-incf arg)
    (js-beginning-of-defun)
    (js-beginning-of-defun)
    (unless (bobp)
      (js-end-of-defun)))

  (while (> arg 0)
    (cl-decf arg)
    ;; look for function backward. if we're inside it, go to that
    ;; function's end. otherwise, search for the next function's end and
    ;; go there
    (if js-flat-functions
        (js--end-of-defun-flat)

      ;; if we're doing nested functions, see whether we're in the
      ;; prologue. If we are, go to the end of the function; otherwise,
      ;; call js--end-of-defun-nested to do the real work
      (let ((prologue-begin (js--function-prologue-beginning)))
        (cond ((and prologue-begin (<=3D prologue-begin (point)))
               (goto-char prologue-begin)
               (re-search-forward "\\_<function")
               (goto-char (match-beginning 0))
               (js--forward-function-decl)
               (forward-list))

              (t (js--end-of-defun-nested)))))))

(defun js--beginning-of-macro (&optional lim)
  (let ((here (point)))
    (save-restriction
      (if lim (narrow-to-region lim (point-max)))
      (beginning-of-line)
      (while (eq (char-before (1- (point))) ?\\)
        (forward-line -1))
      (back-to-indentation)
      (if (and (<=3D (point) here)
               (looking-at js--opt-cpp-start))
          t
        (goto-char here)
        nil))))

(defun js--backward-syntactic-ws (&optional lim)
  "Simple implementation of `c-backward-syntactic-ws' for `js-mode'."
  (save-restriction
    (when lim (narrow-to-region lim (point-max)))

    (let ((in-macro (save-excursion (js--beginning-of-macro)))
          (pos (point)))

      (while (progn (unless in-macro (js--beginning-of-macro))
                    (forward-comment most-negative-fixnum)
                    (/=3D (point)
                        (prog1
                            pos
                          (setq pos (point)))))))))

(defun js--forward-syntactic-ws (&optional lim)
  "Simple implementation of `c-forward-syntactic-ws' for `js-mode'."
  (save-restriction
    (when lim (narrow-to-region (point-min) lim))
    (let ((pos (point)))
      (while (progn
               (forward-comment most-positive-fixnum)
               (when (eq (char-after) ?#)
                 (c-end-of-macro))
               (/=3D (point)
                   (prog1
                       pos
                     (setq pos (point)))))))))

;; Like (up-list -1), but only considers lists that end nearby"
(defun js--up-nearby-list ()
  (save-restriction
    ;; Look at a very small region so our computation time doesn't
    ;; explode in pathological cases.
    (narrow-to-region (max (point-min) (- (point) 500)) (point))
    (up-list -1)))

(defun js--inside-param-list-p ()
  "Return non-nil if point is in a function parameter list."
  (ignore-errors
    (save-excursion
      (js--up-nearby-list)
      (and (looking-at "(")
           (progn (forward-symbol -1)
                  (or (looking-at "function")
                      (progn (forward-symbol -1)
                             (looking-at "function"))))))))

(defun js--inside-dojo-class-list-p ()
  "Return non-nil if point is in a Dojo multiple-inheritance class block."
  (ignore-errors
    (save-excursion
      (js--up-nearby-list)
      (let ((list-begin (point)))
        (forward-line 0)
        (and (looking-at js--dojo-class-decl-re)
             (goto-char (match-end 0))
             (looking-at "\"\\s-*,\\s-*\\[")
             (eq (match-end 0) (1+ list-begin)))))))

;;; Font Lock
(defun js--make-framework-matcher (framework &rest regexps)
  "Helper function for building `js--font-lock-keywords'.
Create a byte-compiled function for matching a concatenation of
REGEXPS, but only if FRAMEWORK is in `js-enabled-frameworks'."
  (let ((regexp (apply #'concat regexps)))
    (lambda (limit)
      (when (memq framework js-enabled-frameworks)
        (re-search-forward regexp limit t)))))

(defvar-local js--tmp-location nil)

(defun js--forward-destructuring-spec (&optional func)
  "Move forward over a JavaScript destructuring spec.
If FUNC is supplied, call it with no arguments before every
variable name in the spec.  Return true if this was actually a
spec.  FUNC must preserve the match data."
  (pcase (char-after)
    (?\[
     (forward-char)
     (while
         (progn
           (forward-comment most-positive-fixnum)
           (cond ((memq (char-after) '(?\[ ?\{))
                  (js--forward-destructuring-spec func))

                 ((eq (char-after) ?,)
                  (forward-char)
                  t)

                 ((looking-at js--name-re)
                  (and func (funcall func))
                  (goto-char (match-end 0))
                  t))))
     (when (eq (char-after) ?\])
       (forward-char)
       t))

    (?\{
     (forward-char)
     (forward-comment most-positive-fixnum)
     (while
         (when (looking-at js--objfield-re)
           (goto-char (match-end 0))
           (forward-comment most-positive-fixnum)
           (and (cond ((memq (char-after) '(?\[ ?\{))
                       (js--forward-destructuring-spec func))
                      ((looking-at js--name-re)
                       (and func (funcall func))
                       (goto-char (match-end 0))
                       t))
                (progn (forward-comment most-positive-fixnum)
                       (when (eq (char-after) ?\,)
                         (forward-char)
                         (forward-comment most-positive-fixnum)
                         t)))))
     (when (eq (char-after) ?\})
       (forward-char)
       t))))

(defun js--variable-decl-matcher (limit)
  "Font-lock matcher for variable names in a variable declaration.
This is a cc-mode-style matcher that *always* fails, from the
point of view of font-lock.  It applies highlighting directly with
`font-lock-apply-highlight'."
  (condition-case nil
      (save-restriction
        (narrow-to-region (point-min) limit)

        (let ((first t))
          (forward-comment most-positive-fixnum)
          (while
              (and (or first
                       (when (eq (char-after) ?,)
                         (forward-char)
                         (forward-comment most-positive-fixnum)
                         t))
                   (cond ((looking-at js--name-re)
                          (font-lock-apply-highlight
                           '(0 font-lock-variable-name-face))
                          (goto-char (match-end 0)))

                         ((save-excursion
                            (js--forward-destructuring-spec))

                          (js--forward-destructuring-spec
                           (lambda ()
                             (font-lock-apply-highlight
                              '(0 font-lock-variable-name-face)))))))

            (forward-comment most-positive-fixnum)
            (when (eq (char-after) ?=3D)
              (forward-char)
              (js--forward-expression)
              (forward-comment most-positive-fixnum))

            (setq first nil))))

    ;; Conditions to handle
    (scan-error nil)
    (end-of-buffer nil))

  ;; Matcher always "fails"
  nil)

;; It wouldn=E2=80=99t be sufficient to font-lock JSX with mere regexps, si=
nce
;; a JSXElement may be nested inside a JS expression within the
;; boundaries of a parent JSXOpeningElement, and such a hierarchy
;; ought to be fontified like JSX, JS, and JSX respectively:
;;
;;   <div attr=3D{void(<div></div>) && void(0)}></div>
;;
;;   <div attr=3D{           =E2=86=90 JSX
;;          void(          =E2=86=90 JS
;;            <div></div>  =E2=86=90 JSX
;;          ) && void(0)   =E2=86=90 JS
;;        }></div>         =E2=86=90 JSX
;;
;; `js-syntax-propertize' unambiguously identifies JSX syntax,
;; including when it=E2=80=99s nested.
;;
;; Using a matcher function for each relevant part, retrieve match
;; data recorded as syntax properties for fontification.

(defconst js-jsx--font-lock-keywords
  `((js-jsx--match-tag-name 0 font-lock-function-name-face t)
    (js-jsx--match-attribute-name 0 font-lock-variable-name-face t)
    (js-jsx--match-text 0 'default t) ; =E2=80=9CUndo=E2=80=9D keyword font=
ification.
    (js-jsx--match-tag-beg)
    (js-jsx--match-tag-end)
    (js-jsx--match-expr))
  "JSX font lock faces and multiline text properties.")

(defun js-jsx--match-tag-name (limit)
  "Match JSXBoundaryElement names, until LIMIT."
  (when js-jsx-syntax
    (let ((pos (next-single-char-property-change (point) 'js-jsx-tag-name n=
il limit))
          value)
      (when (and pos (> pos (point)))
        (goto-char pos)
        (or (and (setq value (get-text-property pos 'js-jsx-tag-name))
                 (progn (set-match-data value) t))
            (js-jsx--match-tag-name limit))))))

(defun js-jsx--match-attribute-name (limit)
  "Match JSXAttribute names, until LIMIT."
  (when js-jsx-syntax
    (let ((pos (next-single-char-property-change (point) 'js-jsx-attribute-=
name nil limit))
          value)
      (when (and pos (> pos (point)))
        (goto-char pos)
        (or (and (setq value (get-text-property pos 'js-jsx-attribute-name))
                 (progn (set-match-data value) t))
            (js-jsx--match-attribute-name limit))))))

(defun js-jsx--match-text (limit)
  "Match JSXText, until LIMIT."
  (when js-jsx-syntax
    (let ((pos (next-single-char-property-change (point) 'js-jsx-text nil l=
imit))
          value)
      (when (and pos (> pos (point)))
        (goto-char pos)
        (or (and (setq value (get-text-property pos 'js-jsx-text))
                 (progn (set-match-data value)
                        (put-text-property (car value) (cadr value) 'font-l=
ock-multiline t)
                        t))
            (js-jsx--match-text limit))))))

(defun js-jsx--match-tag-beg (limit)
  "Match JSXBoundaryElements from start, until LIMIT."
  (when js-jsx-syntax
    (let ((pos (next-single-char-property-change (point) 'js-jsx-tag-beg ni=
l limit))
          value)
      (when (and pos (> pos (point)))
        (goto-char pos)
        (or (and (setq value (get-text-property pos 'js-jsx-tag-beg))
                 (progn (put-text-property pos (cdr value) 'font-lock-multi=
line t) t))
            (js-jsx--match-tag-beg limit))))))

(defun js-jsx--match-tag-end (limit)
  "Match JSXBoundaryElements from end, until LIMIT."
  (when js-jsx-syntax
    (let ((pos (next-single-char-property-change (point) 'js-jsx-tag-end ni=
l limit))
          value)
      (when (and pos (> pos (point)))
        (goto-char pos)
        (or (and (setq value (get-text-property pos 'js-jsx-tag-end))
                 (progn (put-text-property value pos 'font-lock-multiline t=
) t))
            (js-jsx--match-tag-end limit))))))

(defun js-jsx--match-expr (limit)
  "Match JSXExpressionContainers, until LIMIT."
  (when js-jsx-syntax
    (let ((pos (next-single-char-property-change (point) 'js-jsx-expr nil l=
imit))
          value)
      (when (and pos (> pos (point)))
        (goto-char pos)
        (or (and (setq value (get-text-property pos 'js-jsx-expr))
                 (progn (put-text-property pos value 'font-lock-multiline t=
) t))
            (js-jsx--match-expr limit))))))

(defconst js--font-lock-keywords-3
  `(
    ;; This goes before keywords-2 so it gets used preferentially
    ;; instead of the keywords in keywords-2. Don't use override
    ;; because that will override syntactic fontification too, which
    ;; will fontify commented-out directives as if they weren't
    ;; commented out.
    ,@cpp-font-lock-keywords ; from font-lock.el

    ,@js--font-lock-keywords-2

    ("\\.\\(prototype\\)\\_>"
     (1 font-lock-constant-face))

    ;; Highlights class being declared, in parts
    (js--class-decl-matcher
     ,(concat "\\(" js--name-re "\\)\\(?:\\.\\|.*$\\)")
     (goto-char (match-beginning 1))
     nil
     (1 font-lock-type-face))

    ;; Highlights parent class, in parts, if available
    (js--class-decl-matcher
     ,(concat "\\(" js--name-re "\\)\\(?:\\.\\|.*$\\)")
     (if (match-beginning 2)
         (progn
           (setq js--tmp-location (match-end 2))
           (goto-char js--tmp-location)
           (insert "=3D")
           (goto-char (match-beginning 2)))
       (setq js--tmp-location nil)
       (goto-char (line-end-position)))
     (when js--tmp-location
       (save-excursion
         (goto-char js--tmp-location)
         (delete-char 1)))
     (1 font-lock-type-face))

    ;; Highlights parent class
    (js--class-decl-matcher
     (2 font-lock-type-face nil t))

    ;; Dojo needs its own matcher to override the string highlighting
    (,(js--make-framework-matcher
       'dojo
       "^\\s-*dojo\\.declare\\s-*(\""
       "\\(" js--dotted-name-re "\\)"
       "\\(?:\"\\s-*,\\s-*\\(" js--dotted-name-re "\\)\\)?")
     (1 font-lock-type-face t)
     (2 font-lock-type-face nil t))

    ;; Match Dojo base classes. Of course Mojo has to be different
    ;; from everything else under the sun...
    (,(js--make-framework-matcher
       'dojo
       "^\\s-*dojo\\.declare\\s-*(\""
       "\\(" js--dotted-name-re "\\)\"\\s-*,\\s-*\\[")
     ,(concat "[[,]\\s-*\\(" js--dotted-name-re "\\)\\s-*"
              "\\(?:\\].*$\\)?")
     (backward-char)
     (end-of-line)
     (1 font-lock-type-face))

    ;; continued Dojo base-class list
    (,(js--make-framework-matcher
       'dojo
       "^\\s-*" js--dotted-name-re "\\s-*[],]")
     ,(concat "\\(" js--dotted-name-re "\\)"
              "\\s-*\\(?:\\].*$\\)?")
     (if (save-excursion (backward-char)
                         (js--inside-dojo-class-list-p))
         (forward-symbol -1)
       (end-of-line))
     (end-of-line)
     (1 font-lock-type-face))

    ;; variable declarations
    ,(list
      (concat "\\_<\\(const\\|var\\|let\\)\\_>\\|" js--basic-type-re)
      (list #'js--variable-decl-matcher nil nil nil))

    ;; class instantiation
    ,(list
      (concat "\\_<new\\_>\\s-+\\(" js--dotted-name-re "\\)")
      (list 1 'font-lock-type-face))

    ;; instanceof
    ,(list
      (concat "\\_<instanceof\\_>\\s-+\\(" js--dotted-name-re "\\)")
      (list 1 'font-lock-type-face))

    ;; formal parameters
    ,(list
      (concat
       "\\_<function\\_>\\(\\s-+" js--name-re "\\)?\\s-*(\\s-*"
       js--name-start-re)
      (list (concat "\\(" js--name-re "\\)\\(\\s-*).*\\)?")
            '(backward-char)
            '(end-of-line)
            '(1 font-lock-variable-name-face)))

    ;; continued formal parameter list
    ,(list
      (concat
       "^\\s-*" js--name-re "\\s-*[,)]")
      (list js--name-re
            '(if (save-excursion (backward-char)
                                 (js--inside-param-list-p))
                 (forward-symbol -1)
               (end-of-line))
            '(end-of-line)
            '(0 font-lock-variable-name-face)))

    ;; jsx (when enabled)
    ,@js-jsx--font-lock-keywords)
  "Level three font lock for `js-mode'.")

(defun js--inside-pitem-p (pitem)
  "Return whether point is inside the given pitem's header or body."
  (js--ensure-cache)
  (cl-assert (js--pitem-h-begin pitem))
  (cl-assert (js--pitem-paren-depth pitem))

  (and (> (point) (js--pitem-h-begin pitem))
       (or (null (js--pitem-b-end pitem))
           (> (js--pitem-b-end pitem) (point)))))

(defun js--parse-state-at-point ()
  "Parse the JavaScript program state at point.
Return a list of `js--pitem' instances that apply to point, most
specific first.  In the worst case, the current toplevel instance
will be returned."
  (save-excursion
    (save-restriction
      (widen)
      (js--ensure-cache)
      (let ((pstate (or (save-excursion
                          (js--backward-pstate))
                        (list js--initial-pitem))))

        ;; Loop until we either hit a pitem at BOB or pitem ends after
        ;; point (or at point if we're at eob)
        (cl-loop for pitem =3D (car pstate)
                 until (or (eq (js--pitem-type pitem)
                               'toplevel)
                           (js--inside-pitem-p pitem))
                 do (pop pstate))

        pstate))))

(defun js--syntactic-context-from-pstate (pstate)
  "Return the JavaScript syntactic context corresponding to PSTATE."
  (let ((type (js--pitem-type (car pstate))))
    (cond ((memq type '(function macro))
           type)
          ((consp type)
           'class)
          (t 'toplevel))))

(defun js-syntactic-context ()
  "Return the JavaScript syntactic context at point.
When called interactively, also display a message with that
context."
  (interactive)
  (let* ((syntactic-context (js--syntactic-context-from-pstate
                             (js--parse-state-at-point))))

    (when (called-interactively-p 'interactive)
      (message "Syntactic context: %s" syntactic-context))

    syntactic-context))

(defun js--class-decl-matcher (limit)
  "Font lock function used by `js-mode'.
This performs fontification according to `js--class-styles'."
  (when js-enabled-frameworks
    (cl-loop initially (js--ensure-cache limit)
             while (re-search-forward js--quick-match-re limit t)
             for orig-end =3D (match-end 0)
             do (goto-char (match-beginning 0))
             if (cl-loop for style in js--class-styles
                         for decl-re =3D (plist-get style :class-decl)
                         if (and (memq (plist-get style :framework)
                                       js-enabled-frameworks)
                                 (memq (js-syntactic-context)
                                       (plist-get style :contexts))
                                 decl-re
                                 (looking-at decl-re))
                         do (goto-char (match-end 0))
                         and return t)
             return t
             else do (goto-char orig-end))))

(defconst js--font-lock-keywords
  '(js--font-lock-keywords-3 js--font-lock-keywords-1
                                   js--font-lock-keywords-2
                                   js--font-lock-keywords-3)
  "Font lock keywords for `js-mode'.  See `font-lock-keywords'.")

(defun js-font-lock-syntactic-face-function (state)
  "Return syntactic face given STATE."
  (if (nth 3 state)
      'font-lock-string-face
    (if (save-excursion
          (goto-char (nth 8 state))
          (looking-at "/\\*\\*"))
        'font-lock-doc-face
      'font-lock-comment-face)))

(defconst js--syntax-propertize-regexp-regexp
  (rx
   ;; Start of regexp.
   "/"
   (0+ (or
        ;; Match characters outside of a character class.
        (not (any ?\[ ?/ ?\\))
        ;; Match backslash quoted characters.
        (and "\\" not-newline)
        ;; Match character class.
        (and
         "["
         (0+ (or
              (not (any ?\] ?\\))
              (and "\\" not-newline)))
         "]")))
   (group (zero-or-one "/")))
  "Regular expression matching a JavaScript regexp literal.")

(defun js-syntax-propertize-regexp (end)
  (let ((ppss (syntax-ppss)))
    (when (eq (nth 3 ppss) ?/)
      ;; A /.../ regexp.
      (goto-char (nth 8 ppss))
      (when (looking-at js--syntax-propertize-regexp-regexp)
        ;; Don't touch text after END.
        (when (> end (match-end 1))
          (setq end (match-end 1)))
        (put-text-property (match-beginning 1) end
                           'syntax-table (string-to-syntax "\"/"))
        (goto-char end)))))

(defconst js--unary-keyword-re
  (js--regexp-opt-symbol '("await" "delete" "typeof" "void" "yield"))
  "Regexp matching unary operator keywords.")

(defun js--unary-keyword-p (string)
  "Check if STRING is a unary operator keyword in JavaScript."
  (string-match-p js--unary-keyword-re string))

;; Adding `syntax-multiline' text properties to JSX isn=E2=80=99t sufficient
;; to identify multiline JSX when first typing it.  For instance, if
;; the user is typing a JSXOpeningElement for the first time=E2=80=A6
;;
;;   <div
;;       ^ (point)
;;
;; =E2=80=A6and the user inserts a line break after the tag name (before the
;; JSXOpeningElement starting on that line has been unambiguously
;; identified as such), then the `syntax-propertize' region won=E2=80=99t be
;; extended backwards to the start of the JSXOpeningElement:
;;
;;   <div         =E2=86=90 This line wasn't JSX when last edited.
;;     attr=3D"">   =E2=86=90 Despite completing the JSX, the next
;;             ^    `syntax-propertize' region wouldn=E2=80=99t magically
;;                  extend back a few lines.
;;
;; Therefore, to try and recover from this scenario, parse backward
;; from =E2=80=9C>=E2=80=9D to try and find the start of JSXBoundaryElement=
s, and
;; extend the `syntax-propertize' region there.

(defun js--syntax-propertize-extend-region (start end)
  "Extend the START-END region for propertization, if necessary.
=46or use by `syntax-propertize-extend-region-functions'."
  (if js-jsx-syntax (js-jsx--syntax-propertize-extend-region start end)))

(defun js-jsx--syntax-propertize-extend-region (start end)
  "Extend the START-END region for propertization, if necessary.
If any =E2=80=9C>=E2=80=9D in the region appears to be the end of a tag sta=
rting
before the start of the region, extend region backwards to the
start of that tag so parsing may proceed from that point.
=46or use by `syntax-propertize-extend-region-functions'."
  (let (new-start
        forward-sexp-function ; Use the Lisp version.
        parse-sexp-lookup-properties) ; Fix backward-sexp error here.
    (catch 'stop
      (goto-char start)
      (while (re-search-forward ">" end t)
        (catch 'continue
          ;; Check if this is really a right shift bitwise operator
          ;; (=E2=80=9C>>=E2=80=9D or =E2=80=9C>>>=E2=80=9D).
          (unless (or (eq (char-before (1- (point))) ?>)
                      (eq (char-after) ?>))
            (save-excursion
              (backward-char)
              (while (progn (if (=3D (point) (point-min)) (throw 'continue =
nil))
                            (/=3D (char-before) ?<))
                (skip-chars-backward " \t\n")
                (if (=3D (point) (point-min)) (throw 'continue nil))
                (cond
                 ((memq (char-before) '(?\" ?\' ?\` ?\}))
                  (condition-case nil
                      (backward-sexp)
                    (scan-error (throw 'continue nil))))
                 ((memq (char-before) '(?\/ ?\=3D)) (backward-char))
                 ((looking-back js--dotted-name-re (line-beginning-position=
) t)
                  (goto-char (match-beginning 0)))
                 (t (throw 'continue nil))))
              (when (< (point) start)
                (setq new-start (1- (point)))
                (throw 'stop nil)))))))
    (if new-start (cons new-start end))))

;; When applying syntax properties, since `js-syntax-propertize' uses
;; `syntax-propertize-rules' to parse JSXBoundaryElements iteratively
;; and statelessly, whenever we exit such an element, we need to
;; determine the JSX depth.  If >0, then we know to apply syntax
;; properties to JSXText up until the next JSXBoundaryElement occurs.
;; But if the JSX depth is 0, then=E2=80=94importantly=E2=80=94we know to N=
OT parse
;; the following code as JSXText, rather propertize it as regular JS
;; as long as warranted.
;;
;; Also, when indenting code, we need to know if the code we=E2=80=99re try=
ing
;; to indent is on the 2nd or later line of multiline JSX, in which
;; case the code is indented according to XML-like JSX conventions.
;;
;; For the aforementioned reasons, we find ourselves needing to
;; determine whether point is enclosed in JSX or not; and, if so,
;; where the JSX is.  The following functions provide that knowledge.

(defconst js-jsx--tag-start-re
  (concat "\\(" js--dotted-name-re "\\)\\(?:"
          ;; Whitespace is only necessary if an attribute implies JSX.
          "\\(?:\\s-\\|\n\\)*[{/>]"
          "\\|"
          "\\(?:\\s-\\|\n\\)+" js--name-start-re
          "\\)")
  "Regexp unambiguously matching a JSXOpeningElement.")

(defun js-jsx--matched-tag-type ()
  "Determine if the last =E2=80=9C<=E2=80=9D was a JSXBoundaryElement and i=
ts type.
Return `close' for a JSXClosingElement/JSXClosingFragment match,
return `self-closing' for some self-closing JSXOpeningElements,
else return `other'."
  (cond
   ((=3D (char-after) ?/) (forward-char) 'close) ; JSXClosingElement/JSXClo=
singFragment
   ((=3D (char-after) ?>) (forward-char) 'other) ; JSXOpeningFragment
   ((and (looking-at js-jsx--tag-start-re) ; JSXOpeningElement
         (not (js--unary-keyword-p (match-string 1))))
    (goto-char (match-end 0))
    (if (=3D (char-before) ?/) 'self-closing 'other))))

(defconst js-jsx--self-closing-re "/\\s-*>"
  "Regexp matching the end of a self-closing JSXOpeningElement.")

(defun js-jsx--matching-close-tag-pos ()
  "Return position of the closer of the opener before point.
Assuming a JSXOpeningElement or a JSXOpeningFragment is
immediately before point, find a matching JSXClosingElement or
JSXClosingFragment, skipping over any nested JSXElements to find
the match.  Return nil if a match can=E2=80=99t be found."
  (let ((tag-stack 1) tag-pos type last-pos pos)
    (catch 'stop
      (while (and (re-search-forward "<\\s-*" nil t) (not (eobp)))
        ;; Not inside a comment or string.
        (unless (nth 8 (save-excursion (syntax-ppss (match-beginning 0))))
          (when (setq tag-pos (match-beginning 0)
                      type (js-jsx--matched-tag-type))
            (when last-pos
              (setq pos (point))
              (goto-char last-pos)
              (while (re-search-forward js-jsx--self-closing-re pos 'move)
                (setq tag-stack (1- tag-stack))))
            (if (eq type 'close)
                (progn
                  (setq tag-stack (1- tag-stack))
                  (when (=3D tag-stack 0)
                    (throw 'stop tag-pos)))
              ;; JSXOpeningElements that we know are self-closing
              ;; aren=E2=80=99t added to the stack at all (because point is
              ;; already past that syntax).
              (unless (eq type 'self-closing)
                (setq tag-stack (1+ tag-stack))))
            (setq last-pos (point))))))))

(defun js-jsx--enclosing-tag-pos ()
  "Return beginning and end of a JSXElement about point.
Look backward for a JSXElement that both starts before point and
also ends at/after point.  That may be either a self-closing
JSXElement or a JSXOpeningElement/JSXClosingElement pair."
  (let ((start (point)) tag-beg tag-beg-pos tag-end-pos close-tag-pos)
    (while
        (and
         (setq tag-beg (js--backward-text-property 'js-jsx-tag-beg))
         (progn
           (setq tag-beg-pos (point)
                 tag-end-pos (cdr tag-beg))
           (not
            (or
             (and (eq (car tag-beg) 'self-closing)
                  (< start tag-end-pos))
             (and (eq (car tag-beg) 'open)
                  (or (< start tag-end-pos)
                      (progn
                        (unless
                            ;; Try to read a cached close position,
                            ;; but it might not be available yet.
                            (setq close-tag-pos
                                  (get-text-property (point) 'js-jsx-close-=
tag-pos))
                          (save-excursion
                            (goto-char tag-end-pos)
                            (setq close-tag-pos (js-jsx--matching-close-tag=
=2Dpos)))
                          (when close-tag-pos
                            ;; Cache the close position to make future
                            ;; searches faster.
                            (put-text-property
                             (point) (1+ (point))
                             'js-jsx-close-tag-pos close-tag-pos)))
                        ;; The JSXOpeningElement may be unclosed, else
                        ;; the closure must occur at/after the start
                        ;; point (otherwise, a miscellaneous previous
                        ;; JSXOpeningElement has been found, so keep
                        ;; looking backwards for an enclosing one).
                        (or (not close-tag-pos) (<=3D start close-tag-pos))=
)))))))
      ;; Don't return the last tag pos, as it wasn't enclosing.
      (setq tag-beg nil close-tag-pos nil))
    (and tag-beg (list tag-beg-pos tag-end-pos close-tag-pos))))

(defun js-jsx--at-enclosing-tag-child-p ()
  "Return t if point is at an enclosing tag=E2=80=99s child."
  (let ((pos (save-excursion (js-jsx--enclosing-tag-pos))))
    (and pos (>=3D (point) (nth 1 pos)))))

;; We implement `syntax-propertize-function' logic fully parsing JSX
;; in order to provide very accurate JSX indentation, even in the most
;; complex cases (e.g. to indent JSX within a JS expression within a
;; JSXAttribute=E2=80=A6), as over the years users have requested this.  Si=
nce
;; we find so much information during this parse, we later use some of
;; the useful bits for font-locking, too.
;;
;; Some extra effort is devoted to ensuring that no code which could
;; possibly be valid JS is ever misinterpreted as partial JSX, since
;; that would be regressive.
;;
;; We first parse trying to find the minimum number of components
;; necessary to unambiguously identify a JSXBoundaryElement, even if
;; it is a partial one.  If a complete one is parsed, we move on to
;; parse any JSXText.  When that=E2=80=99s terminated, we unwind back to the
;; `syntax-propertize-rules' loop so the next JSXBoundaryElement can
;; be parsed, if any, be it an opening or closing one.

(defun js-jsx--text-range (beg end)
  "Identify JSXText within a =E2=80=9C>/{/}/<=E2=80=9D pair."
  (when (> (- end beg) 0)
    (save-excursion
      (goto-char beg)
      (while (and (skip-chars-forward " \t\n" end) (< (point) end))
        ;; Comments and string quotes don=E2=80=99t serve their usual
        ;; syntactic roles in JSXText; make them plain punctuation to
        ;; negate those roles.
        (when (or (=3D (char-after) ?/) ; comment
                  (=3D (syntax-class (syntax-after (point))) 7)) ; string q=
uote
          (put-text-property (point) (1+ (point)) 'syntax-table '(1)))
        (forward-char)))
    ;; Mark JSXText so it can be font-locked as non-keywords.
    (put-text-property beg (1+ beg) 'js-jsx-text (list beg end (current-buf=
fer)))
    ;; Ensure future propertization beginning from within the
    ;; JSXText determines JSXText context from earlier lines.
    (put-text-property beg end 'syntax-multiline t)))

;; In order to respect the end boundary `syntax-propertize-function'
;; sets, care is taken in the following functions to abort parsing
;; whenever that boundary is reached.

(defun js-jsx--syntax-propertize-tag-text (end)
  "Determine if JSXText is before END and propertize it.
Text within an open/close tag pair may be JSXText.  Temporarily
interrupt JSXText by JSXExpressionContainers, and terminate
JSXText when another JSXBoundaryElement is encountered.  Despite
terminations, all JSXText will be identified once all the
JSXBoundaryElements within an outermost JSXElement=E2=80=99s tree have
been propertized."
  (let ((text-beg (point))
        forward-sexp-function) ; Use Lisp version.
    (catch 'stop
      (while (re-search-forward "[{<]" end t)
        (js-jsx--text-range text-beg (1- (point)))
        (cond
         ((=3D (char-before) ?{)
          (let (expr-beg expr-end)
            (condition-case nil
                (save-excursion
                  (backward-char)
                  (setq expr-beg (point))
                  (forward-sexp)
                  (setq expr-end (point)))
              (scan-error nil))
            ;; Recursively propertize the JSXExpressionContainer=E2=80=99s
            ;; (possibly-incomplete) expression.
            (js-syntax-propertize (1+ expr-beg) (if expr-end (min (1- expr-=
end) end) end))
            ;; Ensure future propertization beginning from within the
            ;; (possibly-incomplete) expression can determine JSXText
            ;; context from earlier lines.
            (put-text-property expr-beg (1+ expr-beg) 'js-jsx-expr (or expr=
=2Dend end)) ; font-lock
            (put-text-property expr-beg (if expr-end (min expr-end end) end=
) 'syntax-multiline t) ; syntax-propertize
            ;; Exit the JSXExpressionContainer if that=E2=80=99s possible,
            ;; else move to the end of the propertized area.
            (goto-char (if expr-end (min expr-end end) end))))
         ((=3D (char-before) ?<)
          (backward-char) ; Ensure the next tag can be propertized.
          (throw 'stop nil)))
        (setq text-beg (point))))))

(defconst js-jsx--attribute-name-re (concat js--name-start-re
                                            "\\(?:\\s_\\|\\sw\\|-\\)*")
  "Like `js--name-re', but matches =E2=80=9C-=E2=80=9D as well.")

(defun js-jsx--syntax-propertize-tag (end)
  "Determine if a JSXBoundaryElement is before END and propertize it.
Disambiguate JSX from inequality operators and arrow functions by
testing for syntax only valid as JSX."
  (let ((tag-beg (1- (point))) tag-end (type 'open)
        name-beg name-match-data expr-attribute-beg unambiguous
        forward-sexp-function) ; Use Lisp version.
    (catch 'stop
      (while (and (< (point) end)
                  (progn (skip-chars-forward " \t\n" end)
                         (< (point) end)))
        (cond
         ((=3D (char-after) ?>)
          ;; Make the closing =E2=80=9C>=E2=80=9D a close parenthesis.
          (put-text-property (point) (1+ (point)) 'syntax-table
                             (eval-when-compile (string-to-syntax ")<")))
          (forward-char)
          (setq unambiguous t)
          (throw 'stop nil))
         ;; Handle a JSXSpreadChild (=E2=80=9C<Foo {...bar}=E2=80=9D) or a
         ;; JSXExpressionContainer as a JSXAttribute value
         ;; (=E2=80=9C<Foo bar=3D{=E2=80=A6}=E2=80=9D).  Check this early i=
n case continuing a
         ;; JSXAttribute parse.
         ((or (and name-beg (=3D (char-after) ?{))
              (setq expr-attribute-beg nil))
          (setq unambiguous t) ; JSXExpressionContainer post tag name =E2=
=87=92 JSX
          (when expr-attribute-beg
            ;; Remember that this JSXExpressionContainer is part of a
            ;; JSXAttribute, as that can affect its expression=E2=80=99s
            ;; indentation.
            (put-text-property
             (point) (1+ (point)) 'js-jsx-expr-attribute expr-attribute-beg)
            (setq expr-attribute-beg nil))
          (let (expr-end)
            (condition-case nil
                (save-excursion
                  (forward-sexp)
                  (setq expr-end (point)))
              (scan-error nil))
            (forward-char)
            (if (>=3D (point) end) (throw 'stop nil))
            (skip-chars-forward " \t\n" end)
            (if (>=3D (point) end) (throw 'stop nil))
            (if (=3D (char-after) ?}) (forward-char) ; Shortcut to bail.
              ;; Recursively propertize the JSXExpressionContainer=E2=80=99s
              ;; expression.
              (js-syntax-propertize (point) (if expr-end (min (1- expr-end)=
 end) end))
              ;; Exit the JSXExpressionContainer if that=E2=80=99s possible,
              ;; else move to the end of the propertized area.
              (goto-char (if expr-end (min expr-end end) end)))))
         ((=3D (char-after) ?/)
          ;; Assume a tag is an open tag until a slash is found, then
          ;; figure out what type it actually is.
          (if (eq type 'open) (setq type (if name-beg 'self-closing 'close)=
))
          (forward-char))
         ((and (not name-beg) (looking-at js--dotted-name-re))
          ;; Don=E2=80=99t match code like =E2=80=9Cif (i < await foo)=E2=
=80=9D
          (if (js--unary-keyword-p (match-string 0)) (throw 'stop nil))
          ;; Save boundaries for later fontification after
          ;; unambiguously determining the code is JSX.
          (setq name-beg (match-beginning 0)
                name-match-data (match-data))
          (goto-char (match-end 0)))
         ((and name-beg (looking-at js-jsx--attribute-name-re))
          (setq unambiguous t) ; Non-unary name followed by 2nd name =E2=87=
=92 JSX
          ;; Save JSXAttribute=E2=80=99s name=E2=80=99s match data for font=
=2Dlocking later.
          (put-text-property (match-beginning 0) (1+ (match-beginning 0))
                             'js-jsx-attribute-name (match-data))
          (goto-char (match-end 0))
          (if (>=3D (point) end) (throw 'stop nil))
          (skip-chars-forward " \t\n" end)
          (if (>=3D (point) end) (throw 'stop nil))
          ;; =E2=80=9C=3D=E2=80=9D is optional for null-valued JSXAttribute=
s.
          (when (=3D (char-after) ?=3D)
            (forward-char)
            (if (>=3D (point) end) (throw 'stop nil))
            (skip-chars-forward " \t\n" end)
            (if (>=3D (point) end) (throw 'stop nil))
            ;; Skip over strings (if possible).  Any
            ;; JSXExpressionContainer here will be parsed in the
            ;; next iteration of the loop.
            (if (memq (char-after) '(?\" ?\' ?\`))
                (progn
                  ;; Record the string=E2=80=99s position so derived modes
                  ;; applying syntactic fontification atypically
                  ;; (e.g. js2-mode) can recognize it as part of JSX.
                  (put-text-property (point) (1+ (point)) 'js-jsx-string t)
                  (condition-case nil
                      (forward-sexp)
                    (scan-error (throw 'stop nil))))
              ;; Save JSXAttribute=E2=80=99s beginning in case we find a
              ;; JSXExpressionContainer as the JSXAttribute=E2=80=99s value=
 which
              ;; we should associate with the JSXAttribute.
              (setq expr-attribute-beg (match-beginning 0)))))
         ;; There is nothing more to check; this either isn=E2=80=99t JSX, =
or
         ;; the tag is incomplete.
         (t (throw 'stop nil)))))
    (when unambiguous
      ;; Save JSXBoundaryElement=E2=80=99s name=E2=80=99s match data for fo=
nt-locking.
      (if name-beg (put-text-property name-beg (1+ name-beg) 'js-jsx-tag-na=
me name-match-data))
      ;; Make the opening =E2=80=9C<=E2=80=9D an open parenthesis.
      (put-text-property tag-beg (1+ tag-beg) 'syntax-table
                         (eval-when-compile (string-to-syntax "(>")))
      ;; Prevent =E2=80=9Cout of range=E2=80=9D errors when typing at the e=
nd of a buffer.
      (setq tag-end (if (eobp) (1- (point)) (point)))
      ;; Mark beginning and end of tag for font-locking.
      (put-text-property tag-beg (1+ tag-beg) 'js-jsx-tag-beg (cons type ta=
g-end))
      (put-text-property tag-end (1+ tag-end) 'js-jsx-tag-end tag-beg)
      ;; Use text properties to extend the syntax-propertize region
      ;; backward to the beginning of the JSXBoundaryElement in the
      ;; future.  Typically the closing angle bracket could suggest
      ;; extending backward, but that would also involve more rigorous
      ;; parsing, and the closing angle bracket may not even exist yet
      ;; if the JSXBoundaryElement is still being typed.
      (put-text-property tag-beg (1+ tag-end) 'syntax-multiline t))
    (if (js-jsx--at-enclosing-tag-child-p) (js-jsx--syntax-propertize-tag-t=
ext end))))

(defconst js-jsx--text-properties
  (list
   'js-jsx-tag-beg nil 'js-jsx-tag-end nil 'js-jsx-close-tag-pos nil
   'js-jsx-tag-name nil 'js-jsx-attribute-name nil 'js-jsx-string nil
   'js-jsx-text nil 'js-jsx-expr nil 'js-jsx-expr-attribute nil)
  "Plist of text properties added by `js-syntax-propertize'.")

(defun js-syntax-propertize (start end)
  ;; JavaScript allows immediate regular expression objects, written /.../.
  (goto-char start)
  (if js-jsx-syntax (remove-text-properties start end js-jsx--text-properti=
es))
  (js-syntax-propertize-regexp end)
  (funcall
   (syntax-propertize-rules
    ;; Distinguish /-division from /-regexp chars (and from /-comment-start=
er).
    ;; FIXME: Allow regexps after infix ops like + ...
    ;; https://developer.mozilla.org/en/JavaScript/Reference/Operators
    ;; We can probably just add +, -, <, >, %, ^, ~, ?, : at which
    ;; point I think only * and / would be missing which could also be adde=
d,
    ;; but need care to avoid affecting the // and */ comment markers.
    ("\\(?:^\\|[=3D([{,:;|&!]\\|\\_<return\\_>\\)\\(?:[ \t]\\)*\\(/\\)[^/*]"
     (1 (ignore
	 (forward-char -1)
         (when (or (not (memq (char-after (match-beginning 0)) '(?\s ?\t)))
                   ;; If the / is at the beginning of line, we have to check
                   ;; the end of the previous text.
                   (save-excursion
                     (goto-char (match-beginning 0))
                     (forward-comment (- (point)))
                     (memq (char-before)
                           (eval-when-compile (append "=3D({[,:;" '(nil))))=
))
           (put-text-property (match-beginning 1) (match-end 1)
                              'syntax-table (string-to-syntax "\"/"))
           (js-syntax-propertize-regexp end)))))
    ("\\`\\(#\\)!" (1 "< b"))
    ("<" (0 (ignore
             (when js-jsx-syntax
               ;; Not inside a comment or string.
               (unless (nth 8 (save-excursion (syntax-ppss (match-beginning=
 0))))
                 (js-jsx--syntax-propertize-tag end)))))))
   (point) end))

(defconst js--prettify-symbols-alist
  '(("=3D>" . ?=E2=87=92)
    (">=3D" . ?=E2=89=A5)
    ("<=3D" . ?=E2=89=A4))
  "Alist of symbol prettifications for JavaScript.")

;;; Indentation

(defconst js--possibly-braceless-keyword-re
  (js--regexp-opt-symbol
   '("catch" "do" "else" "finally" "for" "if" "try" "while" "with"
     "each"))
  "Regexp matching keywords optionally followed by an opening brace.")

(defconst js--declaration-keyword-re
  (regexp-opt '("var" "let" "const") 'words)
  "Regular expression matching variable declaration keywords.")

(defconst js--indent-operator-re
  (concat "[-+*/%<>&^|?:.]\\([^-+*/.]\\|$\\)\\|!?=3D\\|"
          (js--regexp-opt-symbol '("in" "instanceof")))
  "Regexp matching operators that affect indentation of continued expressio=
ns.")

(defun js-jsx--looking-at-start-tag-p ()
  "Non-nil if a JSXOpeningElement immediately follows point."
  (let ((tag-beg (get-text-property (point) 'js-jsx-tag-beg)))
    (and tag-beg (memq (car tag-beg) '(open self-closing)))))

(defun js--looking-at-operator-p ()
  "Return non-nil if point is on a JavaScript operator, other than a comma."
  (save-match-data
    (and (looking-at js--indent-operator-re)
         (or (not (eq (char-after) ?:))
             (save-excursion
               (js--backward-syntactic-ws)
               (when (=3D (char-before) ?\)) (backward-list))
               (and (js--re-search-backward "[?:{]\\|\\_<case\\_>" nil t)
                    (eq (char-after) ??))))
         (not (and
               (eq (char-after) ?/)
               (save-excursion
                 (eq (nth 3 (syntax-ppss)) ?/))))
         (not (and
               (eq (char-after) ?*)
               ;; Generator method (possibly using computed property).
               (looking-at (concat "\\* *\\(?:\\[\\|" js--name-re " *(\\)"))
               (save-excursion
                 (js--backward-syntactic-ws)
                 ;; We might misindent some expressions that would
                 ;; return NaN anyway.  Shouldn't be a problem.
                 (memq (char-before) '(?, ?} ?{)))))
         ;; =E2=80=9C<=E2=80=9D isn=E2=80=99t necessarily an operator in JS=
X.
         (not (and js-jsx-syntax (js-jsx--looking-at-start-tag-p))))))

(defun js--find-newline-backward ()
  "Move backward to the nearest newline that is not in a block comment."
  (let ((continue t)
        (result t))
    (while continue
      (setq continue nil)
      (if (search-backward "\n" nil t)
          (let ((parse (syntax-ppss)))
            ;; We match the end of a // comment but not a newline in a
            ;; block comment.
            (when (nth 4 parse)
              (goto-char (nth 8 parse))
              ;; If we saw a block comment, keep trying.
              (unless (nth 7 parse)
                (setq continue t))))
        (setq result nil)))
    result))

(defun js-jsx--looking-back-at-end-tag-p ()
  "Non-nil if a JSXClosingElement immediately precedes point."
  (get-text-property (point) 'js-jsx-tag-end))

(defun js--continued-expression-p ()
  "Return non-nil if the current line continues an expression."
  (save-excursion
    (back-to-indentation)
    (if (js--looking-at-operator-p)
        (if (eq (char-after) ?/)
            (prog1
                (not (nth 3 (syntax-ppss (1+ (point)))))
              (forward-char -1))
          (or
           (not (memq (char-after) '(?- ?+)))
           (progn
             (forward-comment (- (point)))
             (not (memq (char-before) '(?, ?\[ ?\())))))
      (and (js--find-newline-backward)
           (progn
             (skip-chars-backward " \t")
             (and
              ;; The =E2=80=9C>=E2=80=9D at the end of any JSXBoundaryEleme=
nt isn=E2=80=99t
              ;; part of a continued expression.
              (not (and js-jsx-syntax (js-jsx--looking-back-at-end-tag-p)))
              (progn
                (or (bobp) (backward-char))
                (and (> (point) (point-min))
                     (save-excursion
                       (backward-char)
                       (not (looking-at "[/*]/\\|=3D>")))
                     (js--looking-at-operator-p)
                     (and (progn (backward-char)
                                 (not (looking-at "\\+\\+\\|--\\|/[/*]"))))=
))))))))

(defun js--skip-term-backward ()
  "Skip a term before point; return t if a term was skipped."
  (let ((term-skipped nil))
    ;; Skip backward over balanced parens.
    (let ((progress t))
      (while progress
        (setq progress nil)
        ;; First skip whitespace.
        (skip-syntax-backward " ")
        ;; Now if we're looking at closing paren, skip to the opener.
        ;; This doesn't strictly follow JS syntax, in that we might
        ;; skip something nonsensical like "()[]{}", but it is enough
        ;; if it works ok for valid input.
        (when (memq (char-before) '(?\] ?\) ?\}))
          (setq progress t term-skipped t)
          (backward-list))))
    ;; Maybe skip over a symbol.
    (let ((save-point (point)))
      (if (and (< (skip-syntax-backward "w_") 0)
                 (looking-at js--name-re))
          ;; Skipped.
          (progn
            (setq term-skipped t)
            (skip-syntax-backward " "))
        ;; Did not skip, so restore point.
        (goto-char save-point)))
    (when (and term-skipped (> (point) (point-min)))
      (backward-char)
      (eq (char-after) ?.))))

(defun js--skip-terms-backward ()
  "Skip any number of terms backward.
Move point to the earliest \".\" without changing paren levels.
Returns t if successful, nil if no term was found."
  (when (js--skip-term-backward)
    ;; Found at least one.
    (let ((last-point (point)))
      (while (js--skip-term-backward)
        (setq last-point (point)))
      (goto-char last-point)
      t)))

(defun js--chained-expression-p ()
  "Helper for `js--proper-indentation' that handles chained expressions.
A chained expression is when the current line starts with '.' and the
previous line also has a '.' expression.
This function returns the indentation for the current line if it is
a chained expression line; otherwise nil.
This should only be called while point is at the start of the line's conten=
t,
as determined by `back-to-indentation'."
  (when js-chain-indent
    (save-excursion
      (when (and (eq (char-after) ?.)
                 (js--continued-expression-p)
                 (js--find-newline-backward)
                 (js--skip-terms-backward))
        (current-column)))))

(defun js--end-of-do-while-loop-p ()
  "Return non-nil if point is on the \"while\" of a do-while statement.
Otherwise, return nil.  A braceless do-while statement spanning
several lines requires that the start of the loop is indented to
the same column as the current line."
  (interactive)
  (save-excursion
    (save-match-data
      (when (looking-at "\\s-*\\_<while\\_>")
	(if (save-excursion
	      (skip-chars-backward " \t\n}")
	      (looking-at "[ \t\n]*}"))
	    (save-excursion
	      (backward-list) (forward-symbol -1) (looking-at "\\_<do\\_>"))
          (js--re-search-backward "\\_<do\\_>" (line-beginning-position) t)
	  (or (looking-at "\\_<do\\_>")
	      (let ((saved-indent (current-indentation)))
		(while (and (js--re-search-backward "^\\s-*\\_<" nil t)
			    (/=3D (current-indentation) saved-indent)))
		(and (looking-at "\\s-*\\_<do\\_>")
		     (not (js--re-search-forward
                           "\\_<while\\_>" (line-end-position) t))
		     (=3D (current-indentation) saved-indent)))))))))


(defun js--ctrl-statement-indentation ()
  "Helper function for `js--proper-indentation'.
Return the proper indentation of the current line if it starts
the body of a control statement without braces; otherwise, return
nil."
  (save-excursion
    (back-to-indentation)
    (when (save-excursion
            (and (not (eq (line-beginning-position) (point-min)))
                 (not (looking-at "[{]"))
                 (js--re-search-backward "[[:graph:]]" nil t)
                 (progn
                   (or (eobp) (forward-char))
                   (when (=3D (char-before) ?\)) (backward-list))
                   (skip-syntax-backward " ")
                   (skip-syntax-backward "w_")
                   (looking-at js--possibly-braceless-keyword-re))
                 (memq (char-before) '(?\s ?\t ?\n ?\}))
                 (not (js--end-of-do-while-loop-p))))
      (save-excursion
        (goto-char (match-beginning 0))
        (+ (current-indentation) js-indent-level)))))

(defun js--get-c-offset (symbol anchor)
  (let ((c-offsets-alist
         (list (cons 'c js-comment-lineup-func))))
    (c-get-syntactic-indentation (list (cons symbol anchor)))))

(defun js--same-line (pos)
  (and (>=3D pos (line-beginning-position))
       (<=3D pos (line-end-position))))

(defun js--multi-line-declaration-indentation ()
  "Helper function for `js--proper-indentation'.
Return the proper indentation of the current line if it belongs to a declar=
ation
statement spanning multiple lines; otherwise, return nil."
  (let (forward-sexp-function ; Use Lisp version.
        at-opening-bracket)
    (save-excursion
      (back-to-indentation)
      (when (not (looking-at js--declaration-keyword-re))
        (let ((pt (point)))
          (when (looking-at js--indent-operator-re)
            (goto-char (match-end 0)))
          ;; The "operator" is probably a regexp literal opener.
          (when (nth 3 (syntax-ppss))
            (goto-char pt)))
        (while (and (not at-opening-bracket)
                    (not (bobp))
                    (let ((pos (point)))
                      (save-excursion
                        (js--backward-syntactic-ws)
                        (or (eq (char-before) ?,)
                            (and (not (eq (char-before) ?\;))
                                 (prog2
                                     (skip-syntax-backward ".")
                                     (looking-at js--indent-operator-re)
                                   (js--backward-syntactic-ws))
                                 (not (eq (char-before) ?\;)))
                            (js--same-line pos)))))
          (condition-case nil
              (backward-sexp)
            (scan-error (setq at-opening-bracket t))))
        (when (looking-at js--declaration-keyword-re)
          (goto-char (match-end 0))
          (1+ (current-column)))))))

(defun js--indent-in-array-comp (bracket)
  "Return non-nil if we think we're in an array comprehension.
In particular, return the buffer position of the first `for' kwd."
  (let ((end (point)))
    (save-excursion
      (goto-char bracket)
      (when (looking-at "\\[")
        (forward-char 1)
        (js--forward-syntactic-ws)
        (if (looking-at "[[{]")
            (let (forward-sexp-function) ; Use Lisp version.
              (condition-case nil
                  (progn
                    (forward-sexp)       ; Skip destructuring form.
                    (js--forward-syntactic-ws)
                    (if (and (/=3D (char-after) ?,) ; Regular array.
                             (looking-at "for"))
                        (match-beginning 0)))
                (scan-error
                 ;; Nothing to do here.
                 nil)))
          ;; To skip arbitrary expressions we need the parser,
          ;; so we'll just guess at it.
          (if (and (> end (point)) ; Not empty literal.
                   (re-search-forward "[^,]]* \\(for\\_>\\)" end t)
                   ;; Not inside comment or string literal.
                   (let ((status (parse-partial-sexp bracket (point))))
                     (and (=3D 1 (car status))
                          (not (nth 8 status)))))
              (match-beginning 1)))))))

(defun js--array-comp-indentation (bracket for-kwd)
  (if (js--same-line for-kwd)
      ;; First continuation line.
      (save-excursion
        (goto-char bracket)
        (forward-char 1)
        (skip-chars-forward " \t")
        (current-column))
    (save-excursion
      (goto-char for-kwd)
      (current-column))))

(defun js--maybe-goto-declaration-keyword-end (parse-status)
  "Helper function for `js--proper-indentation'.
Depending on the value of `js-indent-first-init', move
point to the end of a variable declaration keyword so that
indentation is aligned to that column."
  (cond
   ((eq js-indent-first-init t)
    (when (looking-at js--declaration-keyword-re)
      (goto-char (1+ (match-end 0)))))
   ((eq js-indent-first-init 'dynamic)
    (let ((bracket (nth 1 parse-status))
          declaration-keyword-end
          at-closing-bracket-p
          forward-sexp-function ; Use Lisp version.
          comma-p)
      (when (looking-at js--declaration-keyword-re)
        (setq declaration-keyword-end (match-end 0))
        (save-excursion
          (goto-char bracket)
          (setq at-closing-bracket-p
                (condition-case nil
                    (progn
                      (forward-sexp)
                      t)
                  (error nil)))
          (when at-closing-bracket-p
            (while (forward-comment 1))
            (setq comma-p (looking-at-p ","))))
        (when comma-p
          (goto-char (1+ declaration-keyword-end))))))))

(defconst js--line-terminating-arrow-re "=3D>\\s-*\\(/[/*]\\|$\\)"
  "Regexp matching the last \"=3D>\" (arrow) token on a line.
Whitespace and comments around the arrow are ignored.")

(defun js--broken-arrow-terminates-line-p ()
  "Helper function for `js--proper-indentation'.
Return non-nil if the last non-comment, non-whitespace token of the
current line is the \"=3D>\" token (of an arrow function)."
  (let ((from (point)))
    (end-of-line)
    (re-search-backward js--line-terminating-arrow-re from t)))

;; When indenting, we want to know if the line is=E2=80=A6
;;
;;   - within a multiline JSXElement, or
;;   - within a string in a JSXBoundaryElement, or
;;   - within JSXText, or
;;   - within a JSXAttribute=E2=80=99s multiline JSXExpressionContainer.
;;
;; In these cases, special XML-like indentation rules for JSX apply.
;; If JS is nested within JSX, then indentation calculations may be
;; combined, such that JS indentation is =E2=80=9Crelative=E2=80=9D to the =
JSX=E2=80=99s.
;;
;; Therefore, functions below provide such contextual information, and
;; `js--proper-indentation' may call itself once recursively in order
;; to finish calculating that =E2=80=9Crelative=E2=80=9D JS+JSX indentation.

(defun js-jsx--context ()
  "Determine JSX context and move to enclosing JSX."
  (let ((pos (point))
        (parse-status (syntax-ppss))
        (enclosing-tag-pos (js-jsx--enclosing-tag-pos)))
    (when enclosing-tag-pos
      (if (< pos (nth 1 enclosing-tag-pos))
          (if (nth 3 parse-status)
              (list 'string (nth 8 parse-status))
            (list 'tag (nth 0 enclosing-tag-pos) (nth 1 enclosing-tag-pos)))
        (list 'text (nth 0 enclosing-tag-pos) (nth 2 enclosing-tag-pos))))))

(defun js-jsx--contextual-indentation (line context)
  "Calculate indentation column for LINE from CONTEXT.
The column calculation is based off of `sgml-calculate-indent'."
  (pcase (nth 0 context)

    ('string
     ;; Go back to previous non-empty line.
     (while (and (> (point) (nth 1 context))
		 (zerop (forward-line -1))
		 (looking-at "[ \t]*$")))
     (if (> (point) (nth 1 context))
	 ;; Previous line is inside the string.
	 (current-indentation)
       (goto-char (nth 1 context))
       (1+ (current-column))))

    ('tag
     ;; Special JSX indentation rule: a =E2=80=9Cdangling=E2=80=9D closing =
angle
     ;; bracket on its own line is indented at the same level as the
     ;; opening angle bracket of the JSXElement.  Otherwise, indent
     ;; JSXAttribute space like SGML.
     (if (and
          js-jsx-align->-with-<
          (progn
            (goto-char (nth 2 context))
            (and (=3D line (line-number-at-pos))
                 (looking-back "^\\s-*/?>" (line-beginning-position)))))
         (progn
           (goto-char (nth 1 context))
           (current-column))
       ;; Indent JSXAttribute space like SGML.
       (goto-char (nth 1 context))
       ;; Skip tag name:
       (skip-chars-forward " \t")
       (skip-chars-forward "^ \t\n")
       (skip-chars-forward " \t")
       (if (not (eolp))
	   (current-column)
         ;; This is the first attribute: indent.
         (goto-char (+ (nth 1 context) js-jsx-attribute-offset))
         (+ (current-column) (or js-jsx-indent-level js-indent-level)))))

    ('text
     ;; Indent to reflect nesting.
     (goto-char (nth 1 context))
     (+ (current-column)
        ;; The last line isn=E2=80=99t nested, but the rest are.
        (if (or (not (nth 2 context)) ; Unclosed.
                (< line (line-number-at-pos (nth 2 context))))
            (or js-jsx-indent-level js-indent-level)
          0)))

    ))

(defun js-jsx--enclosing-curly-pos ()
  "Return position of enclosing =E2=80=9C{=E2=80=9D in a =E2=80=9C{/}=E2=80=
=9D pair about point."
  (let ((parens (reverse (nth 9 (syntax-ppss)))) paren-pos curly-pos)
    (while
        (and
         (setq paren-pos (car parens))
         (not (when (=3D (char-after paren-pos) ?{)
                (setq curly-pos paren-pos)))
         (setq parens (cdr parens))))
    curly-pos))

(defun js-jsx--goto-outermost-enclosing-curly (limit)
  "Set point to enclosing =E2=80=9C{=E2=80=9D at or closest after LIMIT."
  (let (pos)
    (while
        (and
         (setq pos (js-jsx--enclosing-curly-pos))
         (if (>=3D pos limit) (goto-char pos))
         (> pos limit)))))

(defun js-jsx--expr-attribute-pos (start limit)
  "Look back from START to LIMIT for a JSXAttribute."
  (save-excursion
    (goto-char start) ; Skip the first curly.
    ;; Skip any remaining enclosing curlies until the JSXElement=E2=80=99s
    ;; beginning position; the last curly ought to be one of a
    ;; JSXExpressionContainer, which may refer to its JSXAttribute=E2=80=99s
    ;; beginning position (if it has one).
    (js-jsx--goto-outermost-enclosing-curly limit)
    (get-text-property (point) 'js-jsx-expr-attribute)))

(defvar js-jsx--indent-col nil
  "Baseline column for JS indentation within JSX.")

(defvar js-jsx--indent-attribute-line nil
  "Line relative to which indentation uses JSX as a baseline.")

(defun js-jsx--expr-indentation (parse-status pos col)
  "Indent using PARSE-STATUS; relative to POS, use base COL.
To indent a JSXExpressionContainer=E2=80=99s expression, calculate the JS
indentation, using JSX indentation as the base column when
indenting relative to the beginning line of the
JSXExpressionContainer=E2=80=99s JSXAttribute (if any)."
  (let* ((js-jsx--indent-col col)
         (js-jsx--indent-attribute-line
          (if pos (line-number-at-pos pos))))
    (js--proper-indentation parse-status)))

(defun js-jsx--indentation (parse-status)
  "Helper function for `js--proper-indentation'.
Return the proper indentation of the current line if it is part
of a JSXElement expression spanning multiple lines; otherwise,
return nil."
  (let ((current-line (line-number-at-pos))
        (curly-pos (js-jsx--enclosing-curly-pos))
        nth-context context expr-p beg-line col
        forward-sexp-function) ; Use the Lisp version.
    ;; Find the immediate context for indentation information, but
    ;; keep going to determine that point is at the N+1th line of
    ;; multiline JSX.
    (save-excursion
      (while
          (and
           (setq nth-context (js-jsx--context))
           (progn
             (unless context
               (setq context nth-context)
               (setq expr-p (and curly-pos (< (point) curly-pos))))
             (setq beg-line (line-number-at-pos))
             (and
              (=3D beg-line current-line)
              (or (not curly-pos) (> (point) curly-pos)))))))
    ;; When on the second or later line of JSX, indent as JSX,
    ;; possibly switching back to JS indentation within
    ;; JSXExpressionContainers, possibly using the JSX as a base
    ;; column while switching back to JS indentation.
    (when (and context (> current-line beg-line))
      (save-excursion
        (setq col (js-jsx--contextual-indentation current-line context)))
      (if expr-p
          (js-jsx--expr-indentation
           parse-status (js-jsx--expr-attribute-pos curly-pos (nth 1 contex=
t)) col)
        col))))

(defun js--proper-indentation (parse-status)
  "Return the proper indentation for the current line."
  (save-excursion
    (back-to-indentation)
    (cond ((nth 4 parse-status)    ; inside comment
           (js--get-c-offset 'c (nth 8 parse-status)))
          ((nth 3 parse-status) 0) ; inside string
          ((when (and js-jsx-syntax (not js-jsx--indent-col))
             (save-excursion (js-jsx--indentation parse-status))))
          ((and (eq (char-after) ?#)
                (save-excursion
                  (forward-char 1)
                  (looking-at-p cpp-font-lock-keywords-source-directives)))
           0)
          ((save-excursion (js--beginning-of-macro)) 4)
          ;; Indent array comprehension continuation lines specially.
          ((let ((bracket (nth 1 parse-status))
                 beg)
             (and bracket
                  (not (js--same-line bracket))
                  (setq beg (js--indent-in-array-comp bracket))
                  ;; At or after the first loop?
                  (>=3D (point) beg)
                  (js--array-comp-indentation bracket beg))))
          ((js--chained-expression-p))
          ((js--ctrl-statement-indentation))
          ((js--multi-line-declaration-indentation))
          ((nth 1 parse-status)
	   ;; A single closing paren/bracket should be indented at the
	   ;; same level as the opening statement. Same goes for
	   ;; "case" and "default".
           (let ((same-indent-p (looking-at "[]})]"))
                 (switch-keyword-p (looking-at "default\\_>\\|case\\_>[^:]"=
))
                 (continued-expr-p (js--continued-expression-p)))
             (goto-char (nth 1 parse-status)) ; go to the opening char
             (if (or (not js-indent-align-list-continuation)
                     (looking-at "[({[]\\s-*\\(/[/*]\\|$\\)")
                     (save-excursion (forward-char) (js--broken-arrow-termi=
nates-line-p)))
                 (progn ; nothing following the opening paren/bracket
                   (skip-syntax-backward " ")
                   (when (eq (char-before) ?\)) (backward-list))
                   (back-to-indentation)
                   (js--maybe-goto-declaration-keyword-end parse-status)
                   (let* ((in-switch-p (unless same-indent-p
                                         (looking-at "\\_<switch\\_>")))
                          (same-indent-p (or same-indent-p
                                             (and switch-keyword-p
                                                  in-switch-p)))
                          (indent
                           (+
                            (cond
                             ((and js-jsx--indent-attribute-line
                                   (eq js-jsx--indent-attribute-line
                                       (line-number-at-pos)))
                              js-jsx--indent-col)
                             (t
                              (current-column)))
                            (cond (same-indent-p 0)
                                  (continued-expr-p
                                   (+ (* 2 js-indent-level)
                                      js-expr-indent-offset))
                                  (t
                                   (+ js-indent-level
                                      (pcase (char-after (nth 1 parse-statu=
s))
                                        (?\( js-paren-indent-offset)
                                        (?\[ js-square-indent-offset)
                                        (?\{ js-curly-indent-offset))))))))
                     (if in-switch-p
                         (+ indent js-switch-indent-offset)
                       indent)))
               ;; If there is something following the opening
               ;; paren/bracket, everything else should be indented at
               ;; the same level.
               (unless same-indent-p
                 (forward-char)
                 (skip-chars-forward " \t"))
               (current-column))))

          ((js--continued-expression-p)
           (+ js-indent-level js-expr-indent-offset))
          (t (prog-first-column)))))

(defun js-indent-line ()
  "Indent the current line as JavaScript."
  (interactive)
  (let* ((parse-status
          (save-excursion (syntax-ppss (line-beginning-position))))
         (offset (- (point) (save-excursion (back-to-indentation) (point)))=
))
    (unless (nth 3 parse-status)
      (indent-line-to (js--proper-indentation parse-status))
      (when (> offset 0) (forward-char offset)))))

(defun js-jsx-indent-line ()
  "Indent the current line as JavaScript+JSX."
  (interactive)
  (let ((js-jsx-syntax t)) (js-indent-line)))

;;; Filling

(defvar js--filling-paragraph nil)

;; FIXME: Such redefinitions are bad style.  We should try and use some oth=
er
;; way to get the same result.
(defun js--fill-c-advice (js-fun)
  (lambda (orig-fun &rest args)
    (if js--filling-paragraph
        (funcall js-fun (car args))
      (apply orig-fun args))))

(advice-add 'c-forward-sws
            :around (js--fill-c-advice #'js--forward-syntactic-ws))
(advice-add 'c-backward-sws
            :around (js--fill-c-advice #'js--backward-syntactic-ws))
(advice-add 'c-beginning-of-macro
            :around (js--fill-c-advice #'js--beginning-of-macro))

(define-obsolete-function-alias 'js-c-fill-paragraph #'js-fill-paragraph "2=
7.1")
(defun js-fill-paragraph (&optional justify)
  "Fill the paragraph for Javascript code."
  (interactive "*P")
  (let ((js--filling-paragraph t)
        (fill-paragraph-function #'c-fill-paragraph))
    (c-fill-paragraph justify)))

(defun js-do-auto-fill ()
  (let ((js--filling-paragraph t))
    (c-do-auto-fill)))

;;; Type database and Imenu

;; We maintain a cache of semantic information, i.e., the classes and
;; functions we've encountered so far. In order to avoid having to
;; re-parse the buffer on every change, we cache the parse state at
;; each interesting point in the buffer. Each parse state is a
;; modified copy of the previous one, or in the case of the first
;; parse state, the empty state.
;;
;; The parse state itself is just a stack of js--pitem
;; instances. It starts off containing one element that is never
;; closed, that is initially js--initial-pitem.
;;


(defun js--pitem-format (pitem)
  (let ((name (js--pitem-name pitem))
        (type (js--pitem-type pitem)))

    (format "name:%S type:%S"
            name
            (if (atom type)
                type
              (plist-get type :name)))))

(defun js--make-merged-item (item child name-parts)
  "Helper function for `js--splice-into-items'.
Return a new item that is the result of merging CHILD into
ITEM.  NAME-PARTS is a list of parts of the name of CHILD
that we haven't consumed yet."
  (js--debug "js--make-merged-item: {%s} into {%s}"
                   (js--pitem-format child)
                   (js--pitem-format item))

  ;; If the item we're merging into isn't a class, make it into one
  (unless (consp (js--pitem-type item))
    (js--debug "js--make-merged-item: changing dest into class")
    (setq item (make-js--pitem
                :children (list item)

                ;; Use the child's class-style if it's available
                :type (if (atom (js--pitem-type child))
                          js--dummy-class-style
                  (js--pitem-type child))

                :name (js--pitem-strname item))))

  ;; Now we can merge either a function or a class into a class
  (cons (cond
         ((cdr name-parts)
          (js--debug "js--make-merged-item: recursing")
          ;; if we have more name-parts to go before we get to the
          ;; bottom of the class hierarchy, call the merger
          ;; recursively
          (js--splice-into-items (car item) child
                                       (cdr name-parts)))

         ((atom (js--pitem-type child))
          (js--debug "js--make-merged-item: straight merge")
          ;; Not merging a class, but something else, so just prepend
          ;; it
          (cons child (car item)))

         (t
          ;; Otherwise, merge the new child's items into those
          ;; of the new class
          (js--debug "js--make-merged-item: merging class contents")
          (append (car child) (car item))))
        (cdr item)))

(defun js--pitem-strname (pitem)
  "Last part of the name of PITEM, as a string or symbol."
  (let ((name (js--pitem-name pitem)))
    (if (consp name)
        (car (last name))
      name)))

(defun js--splice-into-items (items child name-parts)
  "Splice CHILD into the `js--pitem' ITEMS at NAME-PARTS.
If a class doesn't exist in the tree, create it.  Return
the new items list.  NAME-PARTS is a list of strings given
the broken-down class name of the item to insert."

  (let ((top-name (car name-parts))
        (item-ptr items)
        new-items last-new-item new-cons)

    (js--debug "js--splice-into-items: name-parts: %S items:%S"
             name-parts
             (mapcar #'js--pitem-name items))

    (cl-assert (stringp top-name))
    (cl-assert (> (length top-name) 0))

    ;; If top-name isn't found in items, then we build a copy of items
    ;; and throw it away. But that's okay, since most of the time, we
    ;; *will* find an instance.

    (while (and item-ptr
                (cond ((equal (js--pitem-strname (car item-ptr)) top-name)
                       ;; Okay, we found an entry with the right name. Spli=
ce
                       ;; the merged item into the list...
                       (setq new-cons (cons (js--make-merged-item
                                             (car item-ptr) child
                                             name-parts)
                                            (cdr item-ptr)))

                       (if last-new-item
                           (setcdr last-new-item new-cons)
                         (setq new-items new-cons))

                       ;; ...and terminate the loop
                       nil)

                      (t
                       ;; Otherwise, copy the current cons and move onto the
                       ;; text. This is tricky; we keep track of the tail of
                       ;; the list that begins with new-items in
                       ;; last-new-item.
                       (setq new-cons (cons (car item-ptr) nil))
                       (if last-new-item
                           (setcdr last-new-item new-cons)
                         (setq new-items new-cons))
                       (setq last-new-item new-cons)

                       ;; Go to the next cell in items
                       (setq item-ptr (cdr item-ptr))))))

    (if item-ptr
        ;; Yay! We stopped because we found something, not because
        ;; we ran out of items to search. Just return the new
        ;; list.
        (progn
          (js--debug "search succeeded: %S" name-parts)
          new-items)

      ;; We didn't find anything. If the child is a class and we don't
      ;; have any classes to drill down into, just push that class;
      ;; otherwise, make a fake class and carry on.
      (js--debug "search failed: %S" name-parts)
      (cons (if (cdr name-parts)
                ;; We have name-parts left to process. Make a fake
                ;; class for this particular part...
                (make-js--pitem
                 ;; ...and recursively digest the rest of the name
                 :children (js--splice-into-items
                            nil child (cdr name-parts))
                 :type js--dummy-class-style
                 :name top-name)

              ;; Otherwise, this is the only name we have, so stick
              ;; the item on the front of the list
              child)
            items))))

(defun js--pitem-add-child (pitem child)
  "Copy `js--pitem' PITEM, and push CHILD onto its list of children."
  (cl-assert (integerp (js--pitem-h-begin child)))
  (cl-assert (if (consp (js--pitem-name child))
              (cl-loop for part in (js--pitem-name child)
                       always (stringp part))
            t))

  ;; This trick works because we know (based on our defstructs) that
  ;; the child list is always the first element, and so the second
  ;; element and beyond can be shared when we make our "copy".
  (cons

   (let ((name (js--pitem-name child))
         (type (js--pitem-type child)))

     (cond ((cdr-safe name) ; true if a list of at least two elements
            ;; Use slow path because we need class lookup
            (js--splice-into-items (car pitem) child name))

           ((and (consp type)
                 (plist-get type :prototype))

            ;; Use slow path because we need class merging. We know
            ;; name is a list here because down in
            ;; `js--ensure-cache', we made sure to only add
            ;; class entries with lists for :name
            (cl-assert (consp name))
            (js--splice-into-items (car pitem) child name))

           (t
            ;; Fast path
            (cons child (car pitem)))))

   (cdr pitem)))

(defun js--maybe-make-marker (location)
  "Return a marker for LOCATION if `imenu-use-markers' is non-nil."
  (if imenu-use-markers
      (set-marker (make-marker) location)
    location))

(defun js--pitems-to-imenu (pitems unknown-ctr)
  "Convert PITEMS, a list of `js--pitem' structures, to imenu format."

  (let (imenu-items pitem pitem-type pitem-name subitems)

    (while (setq pitem (pop pitems))
      (setq pitem-type (js--pitem-type pitem))
      (setq pitem-name (js--pitem-strname pitem))
      (when (eq pitem-name t)
        (setq pitem-name (format "[unknown %s]"
                                 (cl-incf (car unknown-ctr)))))

      (cond
       ((memq pitem-type '(function macro))
        (cl-assert (integerp (js--pitem-h-begin pitem)))
        (push (cons pitem-name
                    (js--maybe-make-marker
                     (js--pitem-h-begin pitem)))
              imenu-items))

       ((consp pitem-type) ; class definition
        (setq subitems (js--pitems-to-imenu
                        (js--pitem-children pitem)
                        unknown-ctr))
        (cond (subitems
               (push (cons pitem-name subitems)
                     imenu-items))

              ((js--pitem-h-begin pitem)
               (cl-assert (integerp (js--pitem-h-begin pitem)))
               (setq subitems (list
                               (cons "[empty]"
                                     (js--maybe-make-marker
                                      (js--pitem-h-begin pitem)))))
               (push (cons pitem-name subitems)
                     imenu-items))))

       (t (error "Unknown item type: %S" pitem-type))))

    imenu-items))

(defun js--imenu-create-index ()
  "Return an imenu index for the current buffer."
  (save-excursion
    (save-restriction
      (widen)
      (goto-char (point-max))
      (js--ensure-cache)
      (cl-assert (or (=3D (point-min) (point-max))
                  (eq js--last-parse-pos (point))))
      (when js--last-parse-pos
        (let ((state js--state-at-last-parse-pos)
              (unknown-ctr (cons -1 nil)))

          ;; Make sure everything is closed
          (while (cdr state)
            (setq state
                  (cons (js--pitem-add-child (cl-second state) (car state))
                        (cddr state))))

          (cl-assert (=3D (length state) 1))

          ;; Convert the new-finalized state into what imenu expects
          (js--pitems-to-imenu
           (car (js--pitem-children state))
           unknown-ctr))))))

;; Silence the compiler.
(defvar which-func-imenu-joiner-function)

(defun js--which-func-joiner (parts)
  (mapconcat #'identity parts "."))

(defun js--imenu-to-flat (items prefix symbols)
  (cl-loop for item in items
           if (imenu--subalist-p item)
           do (js--imenu-to-flat
               (cdr item) (concat prefix (car item) ".")
               symbols)
           else
           do (let* ((name (concat prefix (car item)))
                     (name2 name)
                     (ctr 0))

                (while (gethash name2 symbols)
                  (setq name2 (format "%s<%d>" name (cl-incf ctr))))

                (puthash name2 (cdr item) symbols))))

(defun js--get-all-known-symbols ()
  "Return a hash table of all JavaScript symbols.
This searches all existing `js-mode' buffers.  Each key is the
name of a symbol (possibly disambiguated with <N>, where N > 1),
and each value is a marker giving the location of that symbol."
  (cl-loop with symbols =3D (make-hash-table :test 'equal)
           with imenu-use-markers =3D t
           for buffer being the buffers
           for imenu-index =3D (with-current-buffer buffer
                               (when (derived-mode-p 'js-mode)
                                 (js--imenu-create-index)))
           do (js--imenu-to-flat imenu-index "" symbols)
           finally return symbols))

(defvar js--symbol-history nil
  "History of entered JavaScript symbols.")

(defun js--read-symbol (symbols-table prompt &optional initial-input)
  "Helper function for `js-find-symbol'.
Read a symbol from SYMBOLS-TABLE, which is a hash table like the
one from `js--get-all-known-symbols', using prompt PROMPT and
initial input INITIAL-INPUT.  Return a cons of (SYMBOL-NAME
=2E LOCATION), where SYMBOL-NAME is a string and LOCATION is a
marker."
  (let ((choice (completing-read
                 prompt
                 (cl-loop for key being the hash-keys of symbols-table
                          collect key)
                 nil t initial-input 'js--symbol-history)))
    (cons choice (gethash choice symbols-table))))

(defun js--guess-symbol-at-point ()
  (let ((bounds (bounds-of-thing-at-point 'symbol)))
    (when bounds
      (save-excursion
        (goto-char (car bounds))
        (when (eq (char-before) ?.)
          (backward-char)
          (setf (car bounds) (point))))
      (buffer-substring (car bounds) (cdr bounds)))))

(declare-function xref-push-marker-stack "xref" (&optional m))

(defun js-find-symbol (&optional arg)
  "Read a JavaScript symbol and jump to it.
With a prefix argument, restrict symbols to those from the
current buffer.  Pushes a mark onto the tag ring just like
`find-tag'."
  (interactive "P")
  (require 'xref)
  (let (symbols marker)
    (if (not arg)
        (setq symbols (js--get-all-known-symbols))
      (setq symbols (make-hash-table :test 'equal))
      (js--imenu-to-flat (js--imenu-create-index)
                               "" symbols))

    (setq marker (cdr (js--read-symbol
                       symbols "Jump to: "
                       (js--guess-symbol-at-point))))

    (xref-push-marker-stack)
    (switch-to-buffer (marker-buffer marker))
    (push-mark)
    (goto-char marker)))

;;; Syntax extensions

(defvar js-syntactic-mode-name t
  "If non-nil, print enabled syntaxes in the mode name.")

(defun js--syntactic-mode-name-part ()
  "Return a string like =E2=80=9C[JSX]=E2=80=9D when `js-jsx-syntax' is ena=
bled."
  (if js-syntactic-mode-name
      (let (syntaxes)
        (if js-jsx-syntax (push "JSX" syntaxes))
        (if syntaxes
            (concat "[" (mapconcat #'identity syntaxes ",") "]")
          ""))
    ""))

(defun js-use-syntactic-mode-name ()
  "Print enabled syntaxes if `js-syntactic-mode-name' is t.
Modes deriving from `js-mode' should call this to ensure that
their `mode-name' updates to show enabled syntax extensions."
  (when (stringp mode-name)
    (setq mode-name `(,mode-name (:eval (js--syntactic-mode-name-part))))))

(defun js-jsx-enable ()
  "Enable JSX in the current buffer."
  (interactive)
  (setq-local js-jsx-syntax t))

;; To make discovering and using syntax extensions features easier for
;; users (who might not read the docs), try to safely and
;; automatically enable syntax extensions based on heuristics.

(defvar js-jsx-regexps
  (list "\\_<\\(?:var\\|let\\|const\\|import\\)\\_>.*?React")
  "Case-sensitive regexps for detecting JSX in JavaScript buffers.
When `js-jsx-detect-syntax' is non-nil and any of these regexps
match text near the beginning of a JavaScript buffer,
`js-jsx-syntax' (which see) will be made buffer-local and set to
t.")

(defun js-jsx--detect-and-enable (&optional arbitrarily)
  "Detect if JSX is likely to be used, and enable it if so.
Might make `js-jsx-syntax' buffer-local and set it to t.  Matches
from the beginning of the buffer, unless optional arg ARBITRARILY
is non-nil.  Return t after enabling, nil otherwise."
  (when (or (and (buffer-file-name)
                 (string-match-p "\\.jsx\\'" (buffer-file-name)))
            (and js-jsx-detect-syntax
                 (save-excursion
                   (unless arbitrarily
                     (goto-char (point-min)))
                   (catch 'match
                     (mapc
                      (lambda (regexp)
                        (when (let (case-fold-search)
                                (re-search-forward regexp 4000 t))
                          (throw 'match t)))
                      js-jsx-regexps)
                     nil))))
    (js-jsx-enable)
    t))

(defun js-jsx--detect-after-change (beg end _len)
  "Detect if JSX is likely to be used after a change.
This function is intended for use in `after-change-functions'."
  (when (<=3D end 4000)
    (save-excursion
      (goto-char beg)
      (beginning-of-line)
      (save-restriction
        (narrow-to-region (point) end)
        (when (js-jsx--detect-and-enable 'arbitrarily)
          (remove-hook 'after-change-functions #'js-jsx--detect-after-chang=
e t))))))

;; Ensure all CC Mode "lang variables" are set to valid values.
;; js-mode, however, currently uses only those needed for filling.
(eval-and-compile
  (c-add-language 'js-mode 'java-mode))

(c-lang-defconst c-paragraph-start
  js-mode "\\(@[[:alpha:]]+\\>\\|$\\)")

;;; Tree sitter integration

(defun js--treesit-font-lock-compatibility-definition-feature ()
  "Font lock helper, to handle different releases of tree-sitter-javascript.
Check if a node type is available, then return the right font lock rules
for \"definition\" feature."
  (condition-case nil
      (progn (treesit-query-capture 'javascript '((function_expression) @ca=
p))
             ;; Starting from version 0.20.2 of the grammar.
             '((function_expression
                name: (identifier) @font-lock-function-name-face)
               (variable_declarator
                name: (identifier) @font-lock-function-name-face
                value: [(function_expression) (arrow_function)])))
    (error
     ;; An older version of the grammar.
     '((function
        name: (identifier) @font-lock-function-name-face)
       (variable_declarator
        name: (identifier) @font-lock-function-name-face
        value: [(function) (arrow_function)])))))

(defun js-jsx--treesit-indent-compatibility-bb1f97b ()
  "Indent rules helper, to handle different releases of tree-sitter-javascr=
ipt.
Check if a node type is available, then return the right indent rules."
  ;; handle commit bb1f97b
  (condition-case nil
      (progn (treesit-query-capture 'javascript '((jsx_fragment) @capture))
             `(((match "<" "jsx_fragment") parent 0)
               ((parent-is "jsx_fragment") parent js-indent-level)))
    (error
     `(((match "<" "jsx_text") parent 0)
       ((parent-is "jsx_text") parent js-indent-level)))))

(defvar js--treesit-indent-rules
  (let ((switch-case (rx "switch_" (or "case" "default"))))
    `((javascript
       ((parent-is "program") parent-bol 0)
       ((node-is "}") parent-bol 0)
       ((node-is ")") parent-bol 0)
       ((node-is "]") parent-bol 0)
       ((node-is ">") parent-bol 0)
       ((and (parent-is "comment") c-ts-common-looking-at-star)
        c-ts-common-comment-start-after-first-star -1)
       ((parent-is "comment") prev-adaptive-prefix 0)
       ((parent-is "ternary_expression") parent-bol js-indent-level)
       ((parent-is "member_expression") parent-bol js-indent-level)
       ((node-is ,switch-case) parent-bol 0)
       ;; "{" on the newline.
       ((node-is "statement_block") parent-bol js-indent-level)
       ((parent-is "named_imports") parent-bol js-indent-level)
       ((parent-is "statement_block") parent-bol js-indent-level)
       ((parent-is "variable_declarator") parent-bol js-indent-level)
       ((parent-is "arguments") parent-bol js-indent-level)
       ((parent-is "array") parent-bol js-indent-level)
       ((parent-is "formal_parameters") parent-bol js-indent-level)
       ((parent-is "template_string") no-indent) ; Don't indent the string =
contents.
       ((parent-is "template_substitution") parent-bol js-indent-level)
       ((parent-is "object_pattern") parent-bol js-indent-level)
       ((parent-is "object") parent-bol js-indent-level)
       ((parent-is "pair") parent-bol js-indent-level)
       ((parent-is "arrow_function") parent-bol js-indent-level)
       ((parent-is "parenthesized_expression") parent-bol js-indent-level)
       ((parent-is "binary_expression") parent-bol js-indent-level)
       ((parent-is "class_body") parent-bol js-indent-level)
       ((parent-is ,switch-case) parent-bol js-indent-level)
       ((parent-is "statement_block") parent-bol js-indent-level)
       ((match "while" "do_statement") parent-bol 0)
       ((match "else" "if_statement") parent-bol 0)
       ((parent-is ,(rx (or (seq (or "if" "for" "for_in" "while" "do") "_st=
atement")
                            "else_clause")))
        parent-bol js-indent-level)

       ;; JSX
       ,@(js-jsx--treesit-indent-compatibility-bb1f97b)
       ((node-is "jsx_closing_element") parent 0)
       ((match "jsx_element" "statement") parent js-indent-level)
       ((parent-is "jsx_element") parent js-indent-level)
       ((parent-is "jsx_text") parent-bol js-indent-level)
       ((parent-is "jsx_opening_element") parent js-indent-level)
       ((parent-is "jsx_expression") parent-bol js-indent-level)
       ((match "/" "jsx_self_closing_element") parent 0)
       ((parent-is "jsx_self_closing_element") parent js-indent-level)
       ;; FIXME(Theo): This no-node catch-all should be removed.  When is i=
t needed?
       (no-node parent-bol 0)))))

(defvar js--treesit-keywords
  '("as" "async" "await" "break" "case" "catch" "class" "const" "continue"
    "debugger" "default" "delete" "do" "else" "export" "extends" "finally"
    "for" "from" "function" "get" "if" "import" "in" "instanceof" "let" "ne=
w"
    "of" "return" "set" "static" "switch" "switch" "target" "throw" "try"
    "typeof" "var" "void" "while" "with" "yield")
  "JavaScript keywords for tree-sitter font-locking.")

(defvar js--treesit-operators
  '("=3D" "+=3D" "-=3D" "*=3D" "/=3D" "%=3D" "**=3D" "<<=3D" ">>=3D" ">>>=
=3D" "&=3D" "^=3D"
    "|=3D" "&&=3D" "||=3D" "??=3D" "=3D=3D" "!=3D" "=3D=3D=3D" "!=3D=3D" ">=
" ">=3D" "<" "<=3D" "+"
    "-" "*" "/" "%" "++" "--" "**" "&" "|" "^" "~" "<<" ">>" ">>>"
    "&&" "||" "!")
  "JavaScript operators for tree-sitter font-locking.")

(defvar js--treesit-font-lock-settings
  (treesit-font-lock-rules

   :language 'javascript
   :feature 'comment
   '([(comment) (hash_bang_line)] @font-lock-comment-face)

   :language 'javascript
   :feature 'constant
   '(((identifier) @font-lock-constant-face
      (:match "\\`[A-Z_][0-9A-Z_]*\\'" @font-lock-constant-face))

     [(true) (false) (null)] @font-lock-constant-face)

   :language 'javascript
   :feature 'keyword
   `([,@js--treesit-keywords] @font-lock-keyword-face
     [(this) (super)] @font-lock-keyword-face)

   :language 'javascript
   :feature 'string
   '((regex pattern: (regex_pattern)) @font-lock-regexp-face
     (string) @font-lock-string-face)

   :language 'javascript
   :feature 'string-interpolation
   :override t
   '((template_string) @js--fontify-template-string
     (template_substitution ["${" "}"] @font-lock-misc-punctuation-face))

   :language 'javascript
   :feature 'definition
   `(,@(js--treesit-font-lock-compatibility-definition-feature)

     (class_declaration
      name: (identifier) @font-lock-type-face)

     (function_declaration
      name: (identifier) @font-lock-function-name-face)

     (method_definition
      name: (property_identifier) @font-lock-function-name-face)

     (formal_parameters
      [(identifier) @font-lock-variable-name-face
       (array_pattern (identifier) @font-lock-variable-name-face)
       (object_pattern (shorthand_property_identifier_pattern) @font-lock-v=
ariable-name-face)])

     (variable_declarator
      name: (identifier) @font-lock-variable-name-face)

     (variable_declarator
      name: [(array_pattern (identifier) @font-lock-variable-name-face)
             (object_pattern
              (shorthand_property_identifier_pattern) @font-lock-variable-n=
ame-face)])

     ;; full module imports
     (import_clause (identifier) @font-lock-variable-name-face)
     ;; named imports with aliasing
     (import_clause (named_imports (import_specifier
                                    alias: (identifier) @font-lock-variable=
=2Dname-face)))
     ;; named imports without aliasing
     (import_clause (named_imports (import_specifier
                                    !alias
                                    name: (identifier) @font-lock-variable-=
name-face)))

     ;; full namespace import (* as alias)
     (import_clause (namespace_import (identifier) @font-lock-variable-name=
=2Dface)))

   :language 'javascript
   :feature 'assignment
   '((assignment_expression
      left: (_) @js--treesit-fontify-assignment-lhs))

   :language 'javascript
   :feature 'function
   '((call_expression
      function: [(identifier) @font-lock-function-call-face
                 (member_expression
                  property:
                  (property_identifier) @font-lock-function-call-face)]))

   :language 'javascript
   :feature 'jsx
   '((jsx_opening_element name: (_) @font-lock-function-call-face)
     (jsx_closing_element name: (_) @font-lock-function-call-face)
     (jsx_self_closing_element name: (_) @font-lock-function-call-face)
     (jsx_attribute (property_identifier) @font-lock-constant-face))

   :language 'javascript
   :feature 'property
   '(((property_identifier) @font-lock-property-use-face)
     (pair value: (identifier) @font-lock-variable-use-face)
     ((shorthand_property_identifier) @font-lock-property-use-face))

   :language 'javascript
   :feature 'number
   '((number) @font-lock-number-face
     ((identifier) @font-lock-number-face
      (:match "\\`\\(?:NaN\\|Infinity\\)\\'" @font-lock-number-face)))

   :language 'javascript
   :feature 'operator
   `([,@js--treesit-operators] @font-lock-operator-face
     (ternary_expression ["?" ":"] @font-lock-operator-face))

   :language 'javascript
   :feature 'bracket
   '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)

   :language 'javascript
   :feature 'delimiter
   '((["," "." ";" ":"]) @font-lock-delimiter-face)

   :language 'javascript
   :feature 'escape-sequence
   :override t
   '((escape_sequence) @font-lock-escape-face)

   ;; "document" should be first, to avoid overlap.
   :language 'jsdoc
   :override t
   :feature 'document
   '((document) @font-lock-doc-face)

   :language 'jsdoc
   :override t
   :feature 'keyword
   '((tag_name) @font-lock-constant-face)

   :language 'jsdoc
   :override t
   :feature 'bracket
   '((["{" "}"]) @font-lock-bracket-face)

   :language 'jsdoc
   :override t
   :feature 'property
   '((type) @font-lock-type-face)

   :language 'jsdoc
   :override t
   :feature 'definition
   '((identifier) @font-lock-variable-name-face))
  "Tree-sitter font-lock settings.")

(defun js--fontify-template-string (node override start end &rest _)
  "Fontify template string but not substitution inside it.
NODE is the template_string node.  START and END mark the region
to be fontified.

OVERRIDE is the override flag described in
`treesit-font-lock-rules'."
  ;; You would have thought that the children of the string node spans
  ;; the whole string.  No, the children of the template_string only
  ;; includes the starting "`", any template_substitution, and the
  ;; closing "`".  That's why we have to track BEG instead of just
  ;; fontifying each child.
  (let ((child (treesit-node-child node 0))
        (font-beg (treesit-node-start node)))
    (while child
      (let ((font-end (if (equal (treesit-node-type child)
                                 "template_substitution")
                          (treesit-node-start child)
                        (treesit-node-end child))))
        (setq font-beg (max start font-beg))
        (when (< font-beg end)
          (treesit-fontify-with-override
           font-beg font-end 'font-lock-string-face override start end)))
      (setq font-beg (treesit-node-end child)
            child (treesit-node-next-sibling child)))))

(defvar js--treesit-lhs-identifier-query
  (when (treesit-available-p)
    (treesit-query-compile 'javascript '((identifier) @id
                                         (property_identifier) @id
                                         (shorthand_property_identifier_pat=
tern) @id)))
  "Query that captures identifier and query_identifier.")

(defun js--treesit-fontify-assignment-lhs (node override start end &rest _)
  "Fontify the lhs NODE of an assignment_expression.
=46or OVERRIDE, START, END, see `treesit-font-lock-rules'."
  (dolist (node (treesit-query-capture
                 node js--treesit-lhs-identifier-query nil nil t))
    (treesit-fontify-with-override
     (treesit-node-start node) (treesit-node-end node)
     (pcase (treesit-node-type node)
       ("identifier" 'font-lock-variable-use-face)
       ("property_identifier" 'font-lock-property-use-face)
       ("shorthand_property_identifier_pattern" 'font-lock-variable-use-fac=
e))
     override start end)))

(defun js--treesit-defun-name (node)
  "Return the defun name of NODE.
Return nil if there is no name or if NODE is not a defun node."
  (treesit-node-text
   (treesit-node-child-by-field-name
    (pcase (treesit-node-type node)
      ("lexical_declaration"
       (treesit-search-subtree node "variable_declarator" nil nil 1))
      ((or "function_declaration" "method_definition" "class_declaration")
       node))
    "name")
   t))

(defun js--treesit-valid-imenu-entry (node)
  "Return nil if NODE is a non-top-level \"lexical_declaration\"."
  (pcase (treesit-node-type node)
    ("lexical_declaration" (treesit-node-top-level node))
    (_ t)))

;;; Main Function

;;;###autoload
(define-derived-mode js-base-mode prog-mode "JavaScript"
  "Generic major mode for editing JavaScript.

This mode is intended to be inherited by concrete major modes.
Currently there are `js-mode' and `js-ts-mode'."
  :group 'js
  nil)

;;;###autoload
(define-derived-mode js-mode js-base-mode "JavaScript"
  "Major mode for editing JavaScript."
  :group 'js
  (js--mode-setup))

(defun js--mode-setup ()
  ;; Ensure all CC Mode "lang variables" are set to valid values.
  (c-init-language-vars js-mode)
  (setq-local indent-line-function #'js-indent-line)
  (setq-local beginning-of-defun-function #'js-beginning-of-defun)
  (setq-local end-of-defun-function #'js-end-of-defun)
  (setq-local open-paren-in-column-0-is-defun-start nil)
  (setq-local font-lock-defaults
              (list js--font-lock-keywords nil nil nil nil
                    '(font-lock-syntactic-face-function
                      . js-font-lock-syntactic-face-function)))
  (setq-local syntax-propertize-function #'js-syntax-propertize)
  (add-hook 'syntax-propertize-extend-region-functions
            #'syntax-propertize-multiline 'append 'local)
  (add-hook 'syntax-propertize-extend-region-functions
            #'js--syntax-propertize-extend-region 'append 'local)
  (setq-local prettify-symbols-alist js--prettify-symbols-alist)

  (setq-local parse-sexp-ignore-comments t)
  (setq-local which-func-imenu-joiner-function #'js--which-func-joiner)

  ;; Comments
  (setq-local comment-start "// ")
  (setq-local comment-start-skip "\\(?://+\\|/\\*+\\)\\s *")
  (setq-local comment-end "")
  (setq-local fill-paragraph-function #'js-fill-paragraph)
  (setq-local normal-auto-fill-function #'js-do-auto-fill)

  ;; Parse cache
  (add-hook 'before-change-functions #'js--flush-caches t t)

  ;; Frameworks
  (js--update-quick-match-re)

  ;; Syntax extensions
  (unless (js-jsx--detect-and-enable)
    (add-hook 'after-change-functions #'js-jsx--detect-after-change nil t))
  (js-use-syntactic-mode-name)

  ;; Imenu
  (setq imenu-case-fold-search nil)
  (setq imenu-create-index-function #'js--imenu-create-index)

  ;; for filling, pretend we're cc-mode
  (c-foreign-init-lit-pos-cache)
  (add-hook 'before-change-functions #'c-foreign-truncate-lit-pos-cache nil=
 t)
  (setq-local comment-line-break-function #'c-indent-new-comment-line)
  (setq-local comment-multi-line t)
  (setq-local electric-indent-chars
	      (append "{}():;," electric-indent-chars)) ;FIXME: js2-mode adds "[]*=
".
  (setq-local electric-layout-rules
	      '((?\; . after) (?\{ . after) (?\} . before)))

  (let ((c-buffer-is-cc-mode t))
    ;; FIXME: These are normally set by `c-basic-common-init'.  Should
    ;; we call it instead?  (Bug#6071)
    (make-local-variable 'paragraph-start)
    (make-local-variable 'paragraph-separate)
    (make-local-variable 'paragraph-ignore-fill-prefix)
    (make-local-variable 'adaptive-fill-mode)
    (make-local-variable 'adaptive-fill-regexp)
    ;; While the full CC Mode style system is not yet in use, set the
    ;; pertinent style variables manually.
    (c-initialize-builtin-style)
    (let ((style (cc-choose-style-for-mode 'js-mode c-default-style)))
      (c-set-style style))
    (setq c-block-comment-prefix "* "
          c-comment-prefix-regexp "//+\\|\\**")
    (c-setup-paragraph-variables))

  ;; Important to fontify the whole buffer syntactically! If we don't,
  ;; then we might have regular expression literals that aren't marked
  ;; as strings, which will screw up parse-partial-sexp, scan-lists,
  ;; etc. and produce maddening "unbalanced parenthesis" errors.
  ;; When we attempt to find the error and scroll to the portion of
  ;; the buffer containing the problem, JIT-lock will apply the
  ;; correct syntax to the regular expression literal and the problem
  ;; will mysteriously disappear.
  ;; FIXME: We should instead do this fontification lazily by adding
  ;; calls to syntax-propertize wherever it's really needed.
  ;;(syntax-propertize (point-max))
  )

(defvar js--treesit-sentence-nodes
  '("import_statement"
    "debugger_statement"
    "expression_statement"
    "if_statement"
    "switch_statement"
    "for_statement"
    "for_in_statement"
    "while_statement"
    "do_statement"
    "try_statement"
    "with_statement"
    "break_statement"
    "continue_statement"
    "return_statement"
    "throw_statement"
    "empty_statement"
    "labeled_statement"
    "variable_declaration"
    "lexical_declaration"
    "jsx_element"
    "jsx_self_closing_element")
  "Nodes that designate sentences in JavaScript.
See `treesit-thing-settings' for more information.")

(defvar js--treesit-sexp-nodes
  '("expression"
    "parenthesized_expression"
    "formal_parameters"
    "pattern"
    "array"
    "function"
    "string"
    "template_string"
    "template_substitution"
    "escape"
    "template"
    "regex"
    "number"
    "identifier"
    "property_identifier"
    "this"
    "super"
    "true"
    "false"
    "null"
    "undefined"
    "arguments"
    "pair"
    "jsx"
    "statement_block"
    "object"
    "object_pattern"
    "named_imports"
    "class_body")
  "Nodes that designate sexps in JavaScript.
See `treesit-thing-settings' for more information.")

(defvar js--treesit-list-nodes
  '("export_clause"
    "named_imports"
    "statement_block"
    "_for_header"
    "switch_body"
    "parenthesized_expression"
    "object"
    "object_pattern"
    "array"
    "array_pattern"
    "jsx_expression"
    "_jsx_string"
    "string"
    "regex"
    "arguments"
    "class_body"
    "formal_parameters"
    "computed_property_name")
  "Nodes that designate lists in JavaScript.
See `treesit-thing-settings' for more information.")

(defvar js--treesit-jsdoc-beginning-regexp (rx bos "/**")
  "Regular expression matching the beginning of a jsdoc block comment.")

(defvar js--treesit-thing-settings
  `((javascript
     (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
     (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
     (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
     (text ,(js--regexp-opt-symbol '("comment"
                                     "string_fragment")))))
  "Settings for `treesit-thing-settings'.")

(defvar js--treesit-font-lock-feature-list
  '(( comment document definition)
                  ( keyword string)
                  ( assignment constant escape-sequence jsx number
                    pattern string-interpolation)
                  ( bracket delimiter function operator property))
  "Settings for `treesit-font-lock-feature-list'.")

(defvar js--treesit-simple-imenu-settings
  `(("Function" "\\`function_declaration\\'" nil nil)
    ("Variable" "\\`lexical_declaration\\'"
     js--treesit-valid-imenu-entry nil)
    ("Class" ,(rx bos (or "class_declaration"
                          "method_definition")
                  eos)
     nil nil))
  "Settings for `treesit-simple-imenu'.")

(defvar js--treesit-defun-type-regexp
  (rx (or "class_declaration"
          "method_definition"
          "function_declaration"
          "lexical_declaration"))
  "Settings for `treesit-defun-type-regexp'.")

(defvar js--treesit-jsdoc-comment-regexp
  (rx (or "comment" "line_comment" "block_comment" "description"))
  "Regexp for `c-ts-common--comment-regexp'.")

;;;###autoload
(define-derived-mode js-ts-mode js-base-mode "JavaScript"
  "Major mode for editing JavaScript.

\\<js-ts-mode-map>"
  :group 'js
  :syntax-table js-mode-syntax-table
  (when (treesit-ready-p 'javascript)
    ;; Borrowed from `js-mode'.
    (setq-local prettify-symbols-alist js--prettify-symbols-alist)
    (setq-local parse-sexp-ignore-comments t)
    ;; Which-func.
    (setq-local which-func-imenu-joiner-function #'js--which-func-joiner)
    ;; Comment.
    (c-ts-common-comment-setup)
    (setq-local comment-multi-line t)

    ;; Electric-indent.
    (setq-local electric-indent-chars
                (append "{}():;,<>/" electric-indent-chars)) ;FIXME: js2-mo=
de adds "[]*".
    (setq-local electric-layout-rules
	        '((?\; . after) (?\{ . after) (?\} . before)))
    (setq-local syntax-propertize-function #'js-ts--syntax-propertize)

    ;; Tree-sitter setup.
    (setq-local treesit-primary-parser (treesit-parser-create 'javascript))

    ;; Indent.
    (setq-local treesit-simple-indent-rules js--treesit-indent-rules)
    ;; Navigation.
    (setq-local treesit-defun-type-regexp js--treesit-defun-type-regexp)

    (setq-local treesit-defun-name-function #'js--treesit-defun-name)

    (setq-local treesit-thing-settings js--treesit-thing-settings)

    ;; Fontification.
    (setq-local treesit-font-lock-settings js--treesit-font-lock-settings)
    (setq-local treesit-font-lock-feature-list js--treesit-font-lock-featur=
e-list)

    (when (treesit-ready-p 'jsdoc t)
      (setq-local treesit-range-settings
                  (treesit-range-rules
                   :embed 'jsdoc
                   :host 'javascript
                   :local t
                   `(((comment) @capture (:match ,js--treesit-jsdoc-beginni=
ng-regexp @capture)))))

      (setq c-ts-common--comment-regexp js--treesit-jsdoc-comment-regexp))

    ;; Imenu
    (setq-local treesit-simple-imenu-settings js--treesit-simple-imenu-sett=
ings)

    (treesit-major-mode-setup)

    (add-to-list 'auto-mode-alist
                 '("\\(\\.js[mx]\\|\\.har\\)\\'" . js-ts-mode))))

(derived-mode-add-parents 'js-ts-mode '(js-mode))

(defvar js-ts--s-p-query
  (when (treesit-available-p)
    (treesit-query-compile 'javascript
                           '(((regex pattern: (regex_pattern) @regexp))
                             ((variable_declarator value: (jsx_element) @js=
x))
                             ((assignment_expression right: (jsx_element) @=
jsx))
                             ((arguments (jsx_element) @jsx))
                             ((parenthesized_expression (jsx_element) @jsx))
                             ((return_statement (jsx_element) @jsx))))))

(defun js-ts--syntax-propertize (beg end)
  (let ((captures (treesit-query-capture 'javascript js-ts--s-p-query beg e=
nd)))
    (pcase-dolist (`(,name . ,node) captures)
      (let* ((ns (treesit-node-start node))
             (ne (treesit-node-end node))
             (syntax (pcase-exhaustive name
                       ('regexp
                        (cl-decf ns)
                        (cl-incf ne)
                        (string-to-syntax "\"/"))
                       ('jsx
                        (string-to-syntax "|")))))
        (put-text-property ns (1+ ns) 'syntax-table syntax)
        (put-text-property (1- ne) ne 'syntax-table syntax)))))

;;;###autoload
(define-derived-mode js-json-mode prog-mode "JSON"
  :syntax-table js-mode-syntax-table
  (js--mode-setup) ;Reuse most of `js-mode', but not as parent (bug#67463).
  (setq-local js-enabled-frameworks nil)
  ;; Speed up `syntax-ppss': JSON files can be big but can't hold
  ;; regexp matchers nor #! thingies (and `js-enabled-frameworks' is nil).
  (setq-local syntax-propertize-function #'ignore))

;; Since we made JSX support available and automatically-enabled in
;; the base `js-mode' (for ease of use), now `js-jsx-mode' simply
;; serves as one other interface to unconditionally enable JSX in
;; buffers, mostly for backwards-compatibility.
;;
;; Since it is probably more common for packages to integrate with
;; `js-mode' than with `js-jsx-mode', it is therefore probably
;; slightly better for users to use one of the many other methods for
;; enabling JSX syntax.  But using `js-jsx-mode' can=E2=80=99t be that bad
;; either, so we won=E2=80=99t bother users with an obsoletion warning.

;;;###autoload
(define-derived-mode js-jsx-mode js-mode "JavaScript"
  "Major mode for editing JavaScript+JSX.

Simply makes `js-jsx-syntax' buffer-local and sets it to t.

`js-mode' may detect and enable support for JSX automatically if
it appears to be used in a JavaScript file.  You could also
customize `js-jsx-regexps' to improve that detection; or, you
could set `js-jsx-syntax' to t in your init file, or in a
=2Edir-locals.el file, or using file variables; or, you could call
`js-jsx-enable' in `js-mode-hook'.  You may be better served by
one of the aforementioned options instead of using this mode."
  :group 'js
  (js-jsx-enable)
  (setq-local comment-region-function #'js-jsx--comment-region)
  (js-use-syntactic-mode-name))

(defun js-jsx--comment-region (beg end &optional arg)
  (if (or (js-jsx--context)
          (save-excursion
            (skip-chars-forward " \t")
            (js-jsx--looking-at-start-tag-p)))
      (let ((comment-start "{/* ")
            (comment-end " */}"))
        (comment-region-default beg end arg))
    (comment-region-default beg end arg)))

;;;###autoload (defalias 'javascript-mode 'js-mode)

(eval-after-load 'folding
  '(when (fboundp 'folding-add-to-marks-list)
     (folding-add-to-marks-list 'js-mode "// {{{" "// }}}" )))

;;;###autoload
(dolist (name (list "node" "nodejs" "gjs" "rhino"))
  (add-to-list 'interpreter-mode-alist (cons (purecopy name) 'js-mode)))

(provide 'js)

;;; js.el ends here

--nextPart2576831.XAFRqVoOGU
Content-Disposition: attachment; filename="css-mode.el"
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-emacs-lisp; charset="UTF-8"; name="css-mode.el"

;;; css-mode.el --- Major mode to edit CSS files  -*- lexical-binding: t -*-

;; Copyright (C) 2006-2025 Free Software Foundation, Inc.

;; Author: Stefan Monnier <monnier@HIDDEN>
;; Maintainer: Simen Heggest=C3=B8yl <simenheg@HIDDEN>
;; Keywords: hypermedia

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.

;;; Tree-sitter language versions
;;
;; css-ts-mode is known to work with the following languages and version:
;; - tree-sitter-css: v0.23.1-1-g6a442a3
;;
;; We try our best to make builtin modes work with latest grammar
;; versions, so a more recent grammar version has a good chance to work.
;; Send us a bug report if it doesn't.

;;; Commentary:

;; Yet another CSS mode.

;;; Todo:

;; - filling code with auto-fill-mode
;; - fix font-lock errors with multi-line selectors

;;; Code:

(require 'cl-lib)
(require 'color)
(require 'eww)
(require 'imenu)
(require 'seq)
(require 'sgml-mode)
(require 'smie)
(require 'thingatpt)
(eval-when-compile (require 'subr-x)
                   (require 'rx))
(require 'treesit)

(declare-function treesit-parser-create "treesit.c")
(declare-function treesit-induce-sparse-tree "treesit.c")
(declare-function treesit-node-type "treesit.c")
(declare-function treesit-node-start "treesit.c")
(declare-function treesit-node-child "treesit.c")


(defgroup css nil
  "Cascading Style Sheets (CSS) editing mode."
  :group 'languages)

(defconst css-pseudo-class-ids
  '("active" "checked" "default" "disabled" "empty" "enabled" "first"
    "first-child" "first-of-type" "focus" "focus-within" "hover"
    "in-range" "indeterminate" "invalid" "lang" "last-child"
    "last-of-type" "left" "link" "not" "nth-child" "nth-last-child"
    "nth-last-of-type" "nth-of-type" "only-child" "only-of-type"
    "optional" "out-of-range" "read-only" "read-write" "required"
    "right" "root" "scope" "target" "valid" "visited")
  "Identifiers for pseudo-classes.")

(defconst css-pseudo-element-ids
  '("after" "before" "first-letter" "first-line" "selection")
  "Identifiers for pseudo-elements.")

(defconst css-at-ids
  '("charset" "font-face" "import" "keyframes" "media" "namespace"
    "page" "supports")
  "Identifiers that appear in the form @foo.")

(defconst scss-at-ids
  '("at-root" "content" "debug" "each" "else" "else if" "error" "extend"
    "for" "function" "if" "import" "include" "mixin" "return" "use" "warn"
    "while")
  "Additional identifiers that appear in the form @foo in SCSS.")

(defvar-local css--at-ids css-at-ids
  "List of at-rules for the current mode.")

(defconst css-bang-ids
  '("important")
  "Identifiers that appear in the form !foo.")

(defconst scss-bang-ids
  '("default" "global" "optional")
  "Additional identifiers that appear in the form !foo in SCSS.")

(defvar-local css--bang-ids css-bang-ids
  "List of bang-rules for the current mode.")

(defconst css-descriptor-ids
  '("ascent" "baseline" "bbox" "cap-height" "centerline" "definition-src"
    "descent" "font-family" "font-size" "font-stretch" "font-style"
    "font-variant" "font-weight" "mathline" "panose-1" "slope" "src" "stemh"
    "stemv" "topline" "unicode-range" "units-per-em" "widths" "x-height")
  "Identifiers for font descriptors.")

(defconst css-media-ids
  '("all" "aural" "bitmap" "continuous" "grid" "paged" "static" "tactile"
    "visual")
  "Identifiers for types of media.")

(defconst css-property-alist
  ;; CSS 2.1 properties (https://www.w3.org/TR/CSS21/propidx.html).
  ;;
  ;; Properties duplicated by any of the CSS3 modules below have been
  ;; removed.
  '(("azimuth" angle "left-side" "far-left" "left" "center-left"
     "center" "center-right" "right" "far-right" "right-side" "behind"
     "leftwards" "rightwards")
    ("border-collapse" "collapse" "separate")
    ("border-spacing" length)
    ("bottom" length percentage "auto")
    ("caption-side" "top" "bottom")
    ("clear" "none" "left" "right" "both")
    ("content" "normal" "none" string uri counter "attr()"
     "open-quote" "close-quote" "no-open-quote" "no-close-quote")
    ("counter-increment" identifier integer "none")
    ("counter-reset" identifier integer "none")
    ("cue" cue-before cue-after)
    ("cue-after" uri "none")
    ("cue-before" uri "none")
    ("display" "inline" "block" "list-item" "inline-block" "table"
     "inline-table" "table-row-group" "table-header-group"
     "table-footer-group" "table-row" "table-column-group"
     "table-column" "table-cell" "table-caption" "none"
     ;; CSS Flexible Box Layout Module Level 1
     ;; (https://www.w3.org/TR/css3-flexbox/#valdef-display-flex)
     "flex" "inline-flex"
     ;; CSS Grid Layout Module Level 1
     ;; (https://www.w3.org/TR/css-grid-1/#grid-containers)
     "grid" "inline-grid" "subgrid")
    ("elevation" angle "below" "level" "above" "higher" "lower")
    ("empty-cells" "show" "hide")
    ("float" "left" "right" "none")
    ("height" length percentage "auto")
    ("left" length percentage "auto")
    ("line-height" "normal" number length percentage)
    ("list-style" list-style-type list-style-position
     list-style-image)
    ("list-style-image" uri "none")
    ("list-style-position" "inside" "outside")
    ("list-style-type" "disc" "circle" "square" "decimal"
     "decimal-leading-zero" "lower-roman" "upper-roman" "lower-greek"
     "lower-latin" "upper-latin" "armenian" "georgian" "lower-alpha"
     "upper-alpha" "none")
    ("margin" margin-width)
    ("margin-bottom" margin-width)
    ("margin-left" margin-width)
    ("margin-right" margin-width)
    ("margin-top" margin-width)
    ("max-height" length percentage "none")
    ("max-width" length percentage "none")
    ("min-height" length percentage)
    ("min-width" length percentage)
    ("padding" padding-width)
    ("padding-bottom" padding-width)
    ("padding-left" padding-width)
    ("padding-right" padding-width)
    ("padding-top" padding-width)
    ("page-break-after" "auto" "always" "avoid" "left" "right")
    ("page-break-before" "auto" "always" "avoid" "left" "right")
    ("page-break-inside" "avoid" "auto")
    ("pause" time percentage)
    ("pause-after" time percentage)
    ("pause-before" time percentage)
    ("pitch" frequency "x-low" "low" "medium" "high" "x-high")
    ("pitch-range" number)
    ("play-during" uri "mix" "repeat" "auto" "none")
    ("position" "static" "relative" "absolute" "fixed")
    ("quotes" string "none")
    ("richness" number)
    ("right" length percentage "auto")
    ("speak" "normal" "none" "spell-out")
    ("speak-header" "once" "always")
    ("speak-numeral" "digits" "continuous")
    ("speak-punctuation" "code" "none")
    ("speech-rate" number "x-slow" "slow" "medium" "fast" "x-fast"
     "faster" "slower")
    ("stress" number)
    ("table-layout" "auto" "fixed")
    ("top" length percentage "auto")
    ("vertical-align" "baseline" "sub" "super" "top" "text-top"
     "middle" "bottom" "text-bottom" percentage length)
    ("visibility" "visible" "hidden" "collapse")
    ("voice-family" specific-voice generic-voice specific-voice
     generic-voice)
    ("volume" number percentage "silent" "x-soft" "soft" "medium"
     "loud" "x-loud")
    ("width" length percentage "auto")
    ("z-index" "auto" integer)

    ;; CSS Animations
    ;; (https://www.w3.org/TR/css3-animations/#property-index)
    ("animation" single-animation-name time single-timing-function
     single-animation-iteration-count single-animation-direction
     single-animation-fill-mode single-animation-play-state)
    ("animation-delay" time)
    ("animation-direction" single-animation-direction)
    ("animation-duration" time)
    ("animation-fill-mode" single-animation-fill-mode)
    ("animation-iteration-count" single-animation-iteration-count)
    ("animation-name" single-animation-name)
    ("animation-play-state" single-animation-play-state)
    ("animation-timing-function" single-timing-function)

    ;; CSS Backgrounds and Borders Module Level 3
    ;; (https://www.w3.org/TR/css3-background/#property-index)
    ("background" bg-layer final-bg-layer)
    ("background-attachment" attachment)
    ("background-clip" box)
    ("background-color" color)
    ("background-image" bg-image)
    ("background-origin" box)
    ("background-position" position)
    ("background-repeat" repeat-style)
    ("background-size" bg-size)
    ("border" line-width line-style color)
    ("border-bottom" line-width line-style color)
    ("border-bottom-color" color)
    ("border-bottom-left-radius" length percentage)
    ("border-bottom-right-radius" length percentage)
    ("border-bottom-style" line-style)
    ("border-bottom-width" line-width)
    ("border-color" color)
    ("border-image" border-image-source border-image-slice
     border-image-width border-image-outset border-image-repeat)
    ("border-image-outset" length number)
    ("border-image-repeat" "stretch" "repeat" "round" "space")
    ("border-image-slice" number percentage "fill")
    ("border-image-source" "none" image)
    ("border-image-width" length percentage number "auto")
    ("border-left" line-width line-style color)
    ("border-left-color" color)
    ("border-left-style" line-style)
    ("border-left-width" line-width)
    ("border-radius" length percentage)
    ("border-right" line-width line-style color)
    ("border-right-color" color)
    ("border-right-style" line-style)
    ("border-right-width" line-width)
    ("border-style" line-style)
    ("border-top" line-width line-style color)
    ("border-top-color" color)
    ("border-top-left-radius" length percentage)
    ("border-top-right-radius" length percentage)
    ("border-top-style" line-style)
    ("border-top-width" line-width)
    ("border-width" line-width)
    ("box-shadow" "none" shadow)

    ;; CSS Basic User Interface Module Level 3 (CSS3 UI)
    ;; (https://www.w3.org/TR/css3-ui/#property-index)
    ("box-sizing" "content-box" "border-box")
    ("caret-color" "auto" color)
    ("cursor" uri x y "auto" "default" "none" "context-menu" "help"
     "pointer" "progress" "wait" "cell" "crosshair" "text"
     "vertical-text" "alias" "copy" "move" "no-drop" "not-allowed"
     "grab" "grabbing" "e-resize" "n-resize" "ne-resize" "nw-resize"
     "s-resize" "se-resize" "sw-resize" "w-resize" "ew-resize"
     "ns-resize" "nesw-resize" "nwse-resize" "col-resize" "row-resize"
     "all-scroll" "zoom-in" "zoom-out")
    ("nav-down" "auto" id "current" "root" target-name)
    ("nav-left" "auto" id "current" "root" target-name)
    ("nav-right" "auto" id "current" "root" target-name)
    ("nav-up" "auto" id "current" "root" target-name)
    ("outline" outline-color outline-style outline-width)
    ("outline-color" color "invert")
    ("outline-offset" length)
    ("outline-style" "auto" border-style)
    ("outline-width" border-width)
    ("resize" "none" "both" "horizontal" "vertical")
    ("text-overflow" "clip" "ellipsis" string)

    ;; CSS Cascading and Inheritance Level 3
    ;; (https://www.w3.org/TR/css-cascade-3/#property-index)
    ("all")

    ;; CSS Color Module Level 3
    ;; (https://www.w3.org/TR/css3-color/#property)
    ("color" color)
    ("opacity" alphavalue)

    ;; CSS Containment Module Level 2
    ;; (https://www.w3.org/TR/css-contain-2/#property-index)
    ("contain" "none" "strict" "content" "size" "layout" "style" "paint")
    ("content-visibility" "visible" "auto" "hidden")

    ;; CSS Grid Layout Module Level 2
    ;; (https://www.w3.org/TR/css-grid-2/#property-index)
    ("grid" grid-template grid-template-rows "auto-flow" "dense"
     grid-auto-columns grid-auto-rows grid-template-columns)
    ("grid-area" grid-line)
    ("grid-auto-columns" track-size)
    ("grid-auto-flow" "row" "column" "dense")
    ("grid-auto-rows" track-size)
    ("grid-column" grid-line)
    ("grid-column-end" grid-line)
    ("grid-column-gap" length-percentage)
    ("grid-column-start" grid-line)
    ("grid-gap" grid-row-gap grid-column-gap)
    ("grid-row" grid-line)
    ("grid-row-end" grid-line)
    ("grid-row-gap" length-percentage)
    ("grid-row-start" grid-line)
    ("grid-template" "none" grid-template-rows grid-template-columns
     line-names string track-size line-names explicit-track-list)
    ("grid-template-areas" "none" string)
    ("grid-template-columns" "none" track-list auto-track-list "subgrid")
    ("grid-template-rows" "none" track-list auto-track-list "subgrid")

    ;; CSS Box Alignment Module Level 3
    ;; (https://www.w3.org/TR/css-align-3/#property-index)
    ("align-content" baseline-position content-distribution
     overflow-position content-position)
    ("align-items" "normal" "stretch" baseline-position
     overflow-position self-position)
    ("align-self" "auto" "normal" "stretch" baseline-position
     overflow-position self-position)
    ("column-gap" "normal" length-percentage)
    ("gap" row-gap column-gap)
    ("justify-content" "normal" content-distribution overflow-position
     content-position "left" "right")
    ("justify-items" "normal" "stretch" baseline-position
     overflow-position self-position "left" "right" "legacy" "center")
    ("justify-self" "auto" "normal" "stretch" baseline-position
     overflow-position self-position "left" "right")
    ("place-content" align-content justify-content)
    ("place-items" align-items justify-items)
    ("place-self" justify-self align-self)
    ("row-gap" "normal" length-percentage)

    ;; CSS Flexible Box Layout Module Level 1
    ;; (https://www.w3.org/TR/css-flexbox-1/#property-index)
    ("flex" "none" flex-grow flex-shrink flex-basis)
    ("flex-basis" "auto" "content" width)
    ("flex-direction" "row" "row-reverse" "column" "column-reverse")
    ("flex-flow" flex-direction flex-wrap)
    ("flex-grow" number)
    ("flex-shrink" number)
    ("flex-wrap" "nowrap" "wrap" "wrap-reverse")
    ("order" integer)

    ;; CSS Fonts Module Level 3
    ;; (https://www.w3.org/TR/css3-fonts/#property-index)
    ("font" font-style font-variant-css21 font-weight font-stretch
     font-size line-height font-family "caption" "icon" "menu"
     "message-box" "small-caption" "status-bar")
    ("font-family" family-name generic-family)
    ("font-feature-settings" "normal" feature-tag-value)
    ("font-kerning" "auto" "normal" "none")
    ("font-language-override" "normal" string)
    ("font-size" absolute-size relative-size length percentage)
    ("font-size-adjust" "none" number)
    ("font-stretch" "normal" "ultra-condensed" "extra-condensed"
     "condensed" "semi-condensed" "semi-expanded" "expanded"
     "extra-expanded" "ultra-expanded")
    ("font-style" "normal" "italic" "oblique")
    ("font-synthesis" "none" "weight" "style")
    ("font-variant" "normal" "none" common-lig-values
     discretionary-lig-values historical-lig-values
     contextual-alt-values "stylistic()" "historical-forms"
     "styleset()" "character-variant()" "swash()" "ornaments()"
     "annotation()" "small-caps" "all-small-caps" "petite-caps"
     "all-petite-caps" "unicase" "titling-caps" numeric-figure-values
     numeric-spacing-values numeric-fraction-values "ordinal"
     "slashed-zero" east-asian-variant-values east-asian-width-values
     "ruby")
    ("font-variant-alternates" "normal" "stylistic()"
     "historical-forms" "styleset()" "character-variant()" "swash()"
     "ornaments()" "annotation()")
    ("font-variant-caps" "normal" "small-caps" "all-small-caps"
     "petite-caps" "all-petite-caps" "unicase" "titling-caps")
    ("font-variant-east-asian" "normal" east-asian-variant-values
     east-asian-width-values "ruby")
    ("font-variant-ligatures" "normal" "none" common-lig-values
     discretionary-lig-values historical-lig-values
     contextual-alt-values)
    ("font-variant-numeric" "normal" numeric-figure-values
     numeric-spacing-values numeric-fraction-values "ordinal"
     "slashed-zero")
    ("font-variant-position" "normal" "sub" "super")
    ("font-weight" "normal" "bold" "bolder" "lighter" "100" "200"
     "300" "400" "500" "600" "700" "800" "900")

    ;; CSS Fragmentation Module Level 3
    ;; (https://www.w3.org/TR/css-break-3/#property-index)
    ("box-decoration-break" "slice" "clone")
    ("break-after" "auto" "avoid" "avoid-page" "page" "left" "right"
     "recto" "verso" "avoid-column" "column" "avoid-region" "region")
    ("break-before" "auto" "avoid" "avoid-page" "page" "left" "right"
     "recto" "verso" "avoid-column" "column" "avoid-region" "region")
    ("break-inside" "auto" "avoid" "avoid-page" "avoid-column"
     "avoid-region")
    ("orphans" integer)
    ("widows" integer)

    ;; CSS Masking Module Level 1
    ;; (https://www.w3.org/TR/css-masking-1/#property-index)
    ("clip-path" clip-source basic-shape geometry-box "none")
    ("clip-rule" "nonzero" "evenodd")
    ("mask-image" mask-reference)
    ("mask-mode" masking-mode)
    ("mask-repeat" repeat-style)
    ("mask-position" position)
    ("mask-clip" geometry-box "no-clip")
    ("mask-origin" geometry-box)
    ("mask-size" bg-size)
    ("mask-composite" compositing-operator)
    ("mask" mask-layer)
    ("mask-border-source" "none" image)
    ("mask-border-mode" "luminance" "alpha")
    ("mask-border-slice" number percentage "fill")
    ("mask-border-width" length percentage number "auto")
    ("mask-border-outset" length number)
    ("mask-border-repeat" "stretch" "repeat" "round" "space")
    ("mask-border" mask-border-source mask-border-slice
     mask-border-width mask-border-outset mask-border-repeat
     mask-border-mode)
    ("mask-type" "luminance" "alpha")
    ("clip" "rect()" "auto")

    ;; CSS Multi-column Layout Module Level 1
    ;; (https://www.w3.org/TR/css3-multicol/#property-index)
    ;; "break-after", "break-before", and "break-inside" are left out
    ;; below, because they're already included in CSS Fragmentation
    ;; Module Level 3.
    ("column-count" "auto" integer)
    ("column-fill" "auto" "balance" "balance-all")
    ("column-rule" column-rule-width column-rule-style
     column-rule-color)
    ("column-rule-color" color)
    ("column-rule-style" line-style)
    ("column-rule-width" line-width)
    ("column-span" "none" "all")
    ("column-width" "auto" length)
    ("columns" column-width column-count)

    ;; CSS Overflow Module Level 3
    ;; (https://www.w3.org/TR/css-overflow-3/#property-index)
    ("max-lines" "none" integer)
    ("overflow" "visible" "hidden" "scroll" "auto" "paged-x" "paged-y"
     "paged-x-controls" "paged-y-controls" "fragments")
    ("overflow-x" "visible" "hidden" "scroll" "auto" "paged-x"
     "paged-y" "paged-x-controls" "paged-y-controls" "fragments")
    ("overflow-y" "visible" "hidden" "scroll" "auto" "paged-x"
     "paged-y" "paged-x-controls" "paged-y-controls" "fragments")

    ;; CSS Text Decoration Module Level 3
    ;; (https://dev.w3.org/csswg/css-text-decor-3/#property-index)
    ("text-decoration" text-decoration-line text-decoration-style
     text-decoration-color)
    ("text-decoration-color" color)
    ("text-decoration-line" "none" "underline" "overline"
     "line-through" "blink")
    ("text-decoration-skip" "none" "objects" "spaces" "ink" "edges"
     "box-decoration")
    ("text-decoration-style" "solid" "double" "dotted" "dashed"
     "wavy")
    ("text-emphasis" text-emphasis-style text-emphasis-color)
    ("text-emphasis-color" color)
    ("text-emphasis-position" "over" "under" "right" "left")
    ("text-emphasis-style" "none" "filled" "open" "dot" "circle"
     "double-circle" "triangle" "sesame" string)
    ("text-shadow" "none" length color)
    ("text-underline-position" "auto" "under" "left" "right")

    ;; CSS Text Module Level 3
    ;; (https://www.w3.org/TR/css3-text/#property-index)
    ("hanging-punctuation" "none" "first" "force-end" "allow-end"
     "last")
    ("hyphens" "none" "manual" "auto")
    ("letter-spacing" "normal" length)
    ("line-break" "auto" "loose" "normal" "strict")
    ("overflow-wrap" "normal" "break-word")
    ("tab-size" integer length)
    ("text-align" "start" "end" "left" "right" "center" "justify"
     "match-parent")
    ("text-align-last" "auto" "start" "end" "left" "right" "center"
     "justify")
    ("text-indent" length percentage)
    ("text-justify" "auto" "none" "inter-word" "distribute")
    ("text-transform" "none" "capitalize" "uppercase" "lowercase"
     "full-width")
    ("white-space" "normal" "pre" "nowrap" "pre-wrap" "pre-line")
    ("word-break" "normal" "keep-all" "break-all")
    ("word-spacing" "normal" length percentage)
    ("word-wrap" "normal" "break-word")

    ;; CSS Transforms Module Level 1
    ;; (https://www.w3.org/TR/css3-2d-transforms/#property-index)
    ("backface-visibility" "visible" "hidden")
    ("perspective" "none" length)
    ("perspective-origin" "left" "center" "right" "top" "bottom"
     percentage length)
    ("transform" "none" transform-list)
    ("transform-origin" "left" "center" "right" "top" "bottom"
     percentage length)
    ("transform-style" "flat" "preserve-3d")

    ;; CSS Transitions
    ;; (https://www.w3.org/TR/css3-transitions/#property-index)
    ("transition" single-transition)
    ("transition-delay" time)
    ("transition-duration" time)
    ("transition-property" "none" single-transition-property "all")
    ("transition-timing-function" single-transition-timing-function)

    ;; CSS Will Change Module Level 1
    ;; (https://www.w3.org/TR/css-will-change-1/#property-index)
    ("will-change" "auto" animateable-feature)

    ;; CSS Writing Modes Level 3
    ;; (https://www.w3.org/TR/css-writing-modes-3/#property-index)
    ;; "glyph-orientation-vertical" is obsolete and left out.
    ("direction" "ltr" "rtl")
    ("text-combine-upright" "none" "all")
    ("text-orientation" "mixed" "upright" "sideways")
    ("unicode-bidi" "normal" "embed" "isolate" "bidi-override"
     "isolate-override" "plaintext")
    ("writing-mode" "horizontal-tb" "vertical-rl" "vertical-lr")

    ;; Filter Effects Module Level 1
    ;; (https://www.w3.org/TR/filter-effects/#property-index)
    ("color-interpolation-filters" "auto" "sRGB" "linearRGB")
    ("filter" "none" filter-function-list)
    ("flood-color" color)
    ("flood-opacity" number percentage)
    ("lighting-color" color)

    ;; Pointer Events
    ;; (https://www.w3.org/TR/pointerevents/#the-touch-action-css-property)
    ("touch-action" "auto" "none" "pan-x" "pan-y" "manipulation"))
  "Identifiers for properties and their possible values.
The CAR of each entry is the name of a property, while the CDR is
a list of possible values for that property.  String values in
the CDRs represent literal values, while symbols represent one of
the value classes found in `css-value-class-alist'.  If a symbol
is not found in `css-value-class-alist', it's interpreted as a
reference back to one of the properties in this list.  Some
symbols, such as `number' or `identifier', don't produce any
further value candidates, since that list would be infinite.")

(defconst css-property-ids
  (mapcar #'car css-property-alist)
  "Identifiers for properties.")

(defconst css--color-map
  '(("black" . "#000000")
    ("silver" . "#c0c0c0")
    ("gray" . "#808080")
    ("white" . "#ffffff")
    ("maroon" . "#800000")
    ("red" . "#ff0000")
    ("purple" . "#800080")
    ("fuchsia" . "#ff00ff")
    ("magenta" . "#ff00ff")
    ("green" . "#008000")
    ("lime" . "#00ff00")
    ("olive" . "#808000")
    ("yellow" . "#ffff00")
    ("navy" . "#000080")
    ("blue" . "#0000ff")
    ("teal" . "#008080")
    ("aqua" . "#00ffff")
    ("cyan" . "#00ffff")
    ("orange" . "#ffa500")
    ("aliceblue" . "#f0f8ff")
    ("antiquewhite" . "#faebd7")
    ("aquamarine" . "#7fffd4")
    ("azure" . "#f0ffff")
    ("beige" . "#f5f5dc")
    ("bisque" . "#ffe4c4")
    ("blanchedalmond" . "#ffebcd")
    ("blueviolet" . "#8a2be2")
    ("brown" . "#a52a2a")
    ("burlywood" . "#deb887")
    ("cadetblue" . "#5f9ea0")
    ("chartreuse" . "#7fff00")
    ("chocolate" . "#d2691e")
    ("coral" . "#ff7f50")
    ("cornflowerblue" . "#6495ed")
    ("cornsilk" . "#fff8dc")
    ("crimson" . "#dc143c")
    ("darkblue" . "#00008b")
    ("darkcyan" . "#008b8b")
    ("darkgoldenrod" . "#b8860b")
    ("darkgray" . "#a9a9a9")
    ("darkgreen" . "#006400")
    ("darkgrey" . "#a9a9a9")
    ("darkkhaki" . "#bdb76b")
    ("darkmagenta" . "#8b008b")
    ("darkolivegreen" . "#556b2f")
    ("darkorange" . "#ff8c00")
    ("darkorchid" . "#9932cc")
    ("darkred" . "#8b0000")
    ("darksalmon" . "#e9967a")
    ("darkseagreen" . "#8fbc8f")
    ("darkslateblue" . "#483d8b")
    ("darkslategray" . "#2f4f4f")
    ("darkslategrey" . "#2f4f4f")
    ("darkturquoise" . "#00ced1")
    ("darkviolet" . "#9400d3")
    ("deeppink" . "#ff1493")
    ("deepskyblue" . "#00bfff")
    ("dimgray" . "#696969")
    ("dimgrey" . "#696969")
    ("dodgerblue" . "#1e90ff")
    ("firebrick" . "#b22222")
    ("floralwhite" . "#fffaf0")
    ("forestgreen" . "#228b22")
    ("gainsboro" . "#dcdcdc")
    ("ghostwhite" . "#f8f8ff")
    ("gold" . "#ffd700")
    ("goldenrod" . "#daa520")
    ("greenyellow" . "#adff2f")
    ("grey" . "#808080")
    ("honeydew" . "#f0fff0")
    ("hotpink" . "#ff69b4")
    ("indianred" . "#cd5c5c")
    ("indigo" . "#4b0082")
    ("ivory" . "#fffff0")
    ("khaki" . "#f0e68c")
    ("lavender" . "#e6e6fa")
    ("lavenderblush" . "#fff0f5")
    ("lawngreen" . "#7cfc00")
    ("lemonchiffon" . "#fffacd")
    ("lightblue" . "#add8e6")
    ("lightcoral" . "#f08080")
    ("lightcyan" . "#e0ffff")
    ("lightgoldenrodyellow" . "#fafad2")
    ("lightgray" . "#d3d3d3")
    ("lightgreen" . "#90ee90")
    ("lightgrey" . "#d3d3d3")
    ("lightpink" . "#ffb6c1")
    ("lightsalmon" . "#ffa07a")
    ("lightseagreen" . "#20b2aa")
    ("lightskyblue" . "#87cefa")
    ("lightslategray" . "#778899")
    ("lightslategrey" . "#778899")
    ("lightsteelblue" . "#b0c4de")
    ("lightyellow" . "#ffffe0")
    ("limegreen" . "#32cd32")
    ("linen" . "#faf0e6")
    ("mediumaquamarine" . "#66cdaa")
    ("mediumblue" . "#0000cd")
    ("mediumorchid" . "#ba55d3")
    ("mediumpurple" . "#9370db")
    ("mediumseagreen" . "#3cb371")
    ("mediumslateblue" . "#7b68ee")
    ("mediumspringgreen" . "#00fa9a")
    ("mediumturquoise" . "#48d1cc")
    ("mediumvioletred" . "#c71585")
    ("midnightblue" . "#191970")
    ("mintcream" . "#f5fffa")
    ("mistyrose" . "#ffe4e1")
    ("moccasin" . "#ffe4b5")
    ("navajowhite" . "#ffdead")
    ("oldlace" . "#fdf5e6")
    ("olivedrab" . "#6b8e23")
    ("orangered" . "#ff4500")
    ("orchid" . "#da70d6")
    ("palegoldenrod" . "#eee8aa")
    ("palegreen" . "#98fb98")
    ("paleturquoise" . "#afeeee")
    ("palevioletred" . "#db7093")
    ("papayawhip" . "#ffefd5")
    ("peachpuff" . "#ffdab9")
    ("peru" . "#cd853f")
    ("pink" . "#ffc0cb")
    ("plum" . "#dda0dd")
    ("powderblue" . "#b0e0e6")
    ("rosybrown" . "#bc8f8f")
    ("royalblue" . "#4169e1")
    ("saddlebrown" . "#8b4513")
    ("salmon" . "#fa8072")
    ("sandybrown" . "#f4a460")
    ("seagreen" . "#2e8b57")
    ("seashell" . "#fff5ee")
    ("sienna" . "#a0522d")
    ("skyblue" . "#87ceeb")
    ("slateblue" . "#6a5acd")
    ("slategray" . "#708090")
    ("slategrey" . "#708090")
    ("snow" . "#fffafa")
    ("springgreen" . "#00ff7f")
    ("steelblue" . "#4682b4")
    ("tan" . "#d2b48c")
    ("thistle" . "#d8bfd8")
    ("tomato" . "#ff6347")
    ("turquoise" . "#40e0d0")
    ("violet" . "#ee82ee")
    ("wheat" . "#f5deb3")
    ("whitesmoke" . "#f5f5f5")
    ("yellowgreen" . "#9acd32")
    ("rebeccapurple" . "#663399"))
  "Map CSS named colors to their hex RGB value.")

(defconst css-value-class-alist
  `((absolute-size
     "xx-small" "x-small" "small" "medium" "large" "x-large"
     "xx-large")
    (alphavalue number)
    (angle "calc()")
    (animateable-feature "scroll-position" "contents" custom-ident)
    (attachment "scroll" "fixed" "local")
    (auto-repeat "repeat()")
    (auto-track-list line-names fixed-size fixed-repeat auto-repeat)
    (basic-shape "inset()" "circle()" "ellipse()" "polygon()")
    (bg-image image "none")
    (bg-layer bg-image position repeat-style attachment box)
    (bg-size length percentage "auto" "cover" "contain")
    (box "border-box" "padding-box" "content-box")
    (clip-source uri)
    (color
     "rgb()" "rgba()" "hsl()" "hsla()" named-color "transparent"
     "currentColor")
    (common-lig-values "common-ligatures" "no-common-ligatures")
    (compositing-operator "add" "subtract" "intersect" "exclude")
    (contextual-alt-values "contextual" "no-contextual")
    (counter "counter()" "counters()")
    (discretionary-lig-values
     "discretionary-ligatures" "no-discretionary-ligatures")
    (east-asian-variant-values
     "jis78" "jis83" "jis90" "jis04" "simplified" "traditional")
    (east-asian-width-values "full-width" "proportional-width")
    (explicit-track-list line-names track-size)
    (family-name "Courier" "Helvetica" "Times")
    (feature-tag-value string integer "on" "off")
    (filter-function
     "blur()" "brightness()" "contrast()" "drop-shadow()"
     "grayscale()" "hue-rotate()" "invert()" "opacity()" "sepia()"
     "saturate()")
    (filter-function-list filter-function uri)
    (final-bg-layer
     bg-image position repeat-style attachment box color)
    (fixed-breadth length-percentage)
    (fixed-repeat "repeat()")
    (fixed-size fixed-breadth "minmax()")
    (font-variant-css21 "normal" "small-caps")
    (frequency "calc()")
    (generic-family
     "serif" "sans-serif" "cursive" "fantasy" "monospace")
    (generic-voice "male" "female" "child")
    (geometry-box shape-box "fill-box" "stroke-box" "view-box")
    (gradient
     linear-gradient radial-gradient repeating-linear-gradient
     repeating-radial-gradient)
    (grid-line "auto" custom-ident integer "span")
    (historical-lig-values
     "historical-ligatures" "no-historical-ligatures")
    (image uri image-list element-reference gradient)
    (image-list "image()")
    (inflexible-breadth length-percentage "min-content" "max-content"
                        "auto")
    (integer "calc()")
    (length "calc()" number)
    (line-height "normal" number length percentage)
    (line-names custom-ident)
    (line-style
     "none" "hidden" "dotted" "dashed" "solid" "double" "groove"
     "ridge" "inset" "outset")
    (line-width length "thin" "medium" "thick")
    (linear-gradient "linear-gradient()")
    (margin-width "auto" length percentage)
    (mask-layer
     mask-reference masking-mode position bg-size repeat-style
     geometry-box "no-clip" compositing-operator)
    (mask-reference "none" image mask-source)
    (mask-source uri)
    (masking-mode "alpha" "luminance" "auto")
    (named-color . ,(mapcar #'car css--color-map))
    (number "calc()")
    (numeric-figure-values "lining-nums" "oldstyle-nums")
    (numeric-fraction-values "diagonal-fractions" "stacked-fractions")
    (numeric-spacing-values "proportional-nums" "tabular-nums")
    (padding-width length percentage)
    (position
     "left" "center" "right" "top" "bottom" percentage length)
    (baseline-position "left" "right" "baseline")
    (content-distribution
     "space-between" "space-around" "space-evenly" "stretch")
    (overflow-position "unsafe" "safe")
    (content-position "center" "start" "end" "flex-start" "flex-end")
    (self-position
     "center" "start" "end" "self-start" "self-end" "flex-start" "flex-end")
    (radial-gradient "radial-gradient()")
    (relative-size "larger" "smaller")
    (repeat-style
     "repeat-x" "repeat-y" "repeat" "space" "round" "no-repeat")
    (repeating-linear-gradient "repeating-linear-gradient()")
    (repeating-radial-gradient "repeating-radial-gradient()")
    (shadow "inset" length color)
    (shape-box box "margin-box")
    (single-animation-direction
     "normal" "reverse" "alternate" "alternate-reverse")
    (single-animation-fill-mode "none" "forwards" "backwards" "both")
    (single-animation-iteration-count "infinite" number)
    (single-animation-name "none" identifier)
    (single-animation-play-state "running" "paused")
    (single-timing-function single-transition-timing-function)
    (single-transition
     "none" single-transition-property time
     single-transition-timing-function)
    (single-transition-property "all" identifier)
    (single-transition-timing-function
     "ease" "linear" "ease-in" "ease-out" "ease-in-out" "step-start"
     "step-end" "steps()" "cubic-bezier()")
    (specific-voice identifier)
    (target-name string)
    (time "calc()")
    (track-breadth length-percentage flex "min-content" "max-content"
                   "auto")
    (track-list line-names track-size track-repeat)
    (track-repeat "repeat()")
    (track-size track-breadth "minmax()" "fit-content()")
    (transform-list
     "matrix()" "translate()" "translateX()" "translateY()" "scale()"
     "scaleX()" "scaleY()" "rotate()" "skew()" "skewX()" "skewY()"
     "matrix3d()" "translate3d()" "translateZ()" "scale3d()"
     "scaleZ()" "rotate3d()" "rotateX()" "rotateY()" "rotateZ()"
     "perspective()")
    (uri "url()")
    (width length percentage "auto")
    (x number)
    (y number))
  "Property value classes and their values.
The format is similar to that of `css-property-alist', except
that the CARs aren't actual CSS properties, but rather a name for
a class of values, and that symbols in the CDRs always refer to
other entries in this list, not to properties.

The following classes have been left out above because they
cannot be completed sensibly: `custom-ident',
`element-reference', `flex', `id', `identifier',
`length-percentage', `percentage', and `string'.")

(defcustom css-electric-keys '(?\} ?\;) ;; '()
  "Self inserting keys which should trigger re-indentation."
  :version "22.2"
  :type '(repeat character)
  :group 'css)

(defvar css-mode-syntax-table
  (let ((st (make-syntax-table)))
    ;; C-style comments.
    (modify-syntax-entry ?/ ". 14" st)
    (modify-syntax-entry ?* ". 23b" st)
    ;; Strings.
    (modify-syntax-entry ?\" "\"" st)
    (modify-syntax-entry ?\' "\"" st)
    ;; Blocks.
    (modify-syntax-entry ?\{ "(}" st)
    (modify-syntax-entry ?\} "){" st)
    ;; Args in url(...) thingies and other "function calls".
    (modify-syntax-entry ?\( "()" st)
    (modify-syntax-entry ?\) ")(" st)
    ;; To match attributes in selectors.
    (modify-syntax-entry ?\[ "(]" st)
    (modify-syntax-entry ?\] ")[" st)
    ;; Special chars that sometimes come at the beginning of words.
    ;; We'll treat them as symbol constituents.
    (modify-syntax-entry ?@ "_" st)
    (modify-syntax-entry ?# "_" st)
    (modify-syntax-entry ?. "_" st)
    ;; Distinction between words and symbols.
    (modify-syntax-entry ?- "_" st)

    (modify-syntax-entry ?! "." st)
    (modify-syntax-entry ?$ "." st)
    (modify-syntax-entry ?% "." st)
    (modify-syntax-entry ?& "." st)
    (modify-syntax-entry ?+ "." st)
    (modify-syntax-entry ?, "." st)
    (modify-syntax-entry ?< "." st)
    (modify-syntax-entry ?> "." st)
    (modify-syntax-entry ?=3D "." st)
    (modify-syntax-entry ?? "." st)
    st))

(defvar css-mode--menu
  '("CSS"
    :help "CSS-specific features"
    ["Reformat block" fill-paragraph
     :help "Reformat declaration block or fill comment at point"]
    ["Cycle color format" css-cycle-color-format
     :help "Cycle color at point between different formats"]
    "-"
    ["Describe symbol" css-lookup-symbol
     :help "Display documentation for a CSS symbol"]
    ["Complete symbol" completion-at-point
     :help "Complete symbol before point"])
    "Menu bar for `css-mode'")

(defvar-keymap css-mode-map
  :doc "Keymap used in `css-mode'."
  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
  ;; `info-complete-symbol' is not used.
  "<remap> <complete-symbol>" #'completion-at-point
  "C-c C-f" #'css-cycle-color-format
  :menu
  css-mode--menu)

(eval-and-compile
  (defconst css--uri-re
    (concat
     "url\\((\\)[[:space:]]*\\(?:\\\\.\\|[^()[:space:]\n'\"]\\)+"
     "[[:space:]]*\\()\\)")))

(defconst css-syntax-propertize-function
  (syntax-propertize-rules
   (css--uri-re (1 "|") (2 "|"))))

(defconst css-escapes-re
  "\\\\\\(?:[^\000-\037\177]\\|[[:xdigit:]]+[ \n\t\r\f]?\\)")
(defconst css-nmchar-re (concat "\\(?:[-_[:alnum:]]\\|" css-escapes-re "\\)=
"))
(defconst css-nmstart-re (concat "\\(?:[[:alpha:]]\\|" css-escapes-re "\\)"=
))
(defconst css-ident-re ;; (concat css-nmstart-re css-nmchar-re "*")
  ;; Apparently, "at rules" names can start with a dash, e.g. @-moz-keyfram=
es.
  (concat css-nmchar-re "+"))
(defconst css-proprietary-nmstart-re ;; Vendor-specific properties.
  (concat "[-_]" (regexp-opt '("ms" "moz" "o" "khtml" "webkit")) "-"))
(defconst css-name-re (concat css-nmchar-re "+"))

(defconst scss--hash-re "#\\(?:{[$-_[:alnum:]]+}\\|[[:alnum:]]+\\)")

(defface css-selector '((t :inherit font-lock-function-name-face))
  "Face to use for selectors."
  :group 'css)
(defface css-property '((t :inherit font-lock-keyword-face))
  "Face to use for properties."
  :group 'css)
(defface css-proprietary-property '((t :inherit (css-property italic)))
  "Face to use for vendor-specific properties.")

(defun css--selector-regexp (sassy)
  (concat
   "\\(?:"
   (if (not sassy)
       "[-_%*#.>[:alnum:]]+"
     ;; Same as for non-sassy except we do want to allow { and }
     ;; chars in selectors in the case of #{$foo}
     ;; variable interpolation!
     (concat "\\(?:[-_%*#.>&+~[:alnum:]]*" scss--hash-re
             "\\|[-_%*#.>&+~[:alnum:]]+\\)"))
   ;; Even though pseudo-elements should be prefixed by ::, a
   ;; single colon is accepted for backward compatibility.
   "\\(?:\\(:" (regexp-opt (append css-pseudo-class-ids
                                   css-pseudo-element-ids)
                           t)
   "\\|::" (regexp-opt css-pseudo-element-ids t) "\\)\\)?"
   ;; Braces after selectors.
   "\\(?:\\[[^]\n]+\\]\\)?"
   ;; Parentheses after selectors.
   "\\(?:([^)]+)\\)?"
   ;; Main bit over.  But perhaps just [target]?
   "\\|\\[[^]\n]+\\]"
   ;; :root, ::marker and the like.
   "\\|::?[[:alnum:]]+\\(?:([^)]+)\\)?"
   "\\)"))

(defun css--font-lock-keywords (&optional sassy)
  `((,(concat "!\\s-*" (regexp-opt css--bang-ids))
     (0 font-lock-builtin-face))
    ;; Atrules keywords.  IDs not in css-at-ids are valid (ignored).
    ;; In fact the regexp should probably be
    ;; (,(concat "\\(@" css-ident-re "\\)\\([ \t\n][^;{]*\\)[;{]")
    ;;  (1 font-lock-builtin-face))
    ;; Since "An at-rule consists of everything up to and including the next
    ;; semicolon (;) or the next block, whichever comes first."
    (,(concat "@" css-ident-re) (0 font-lock-builtin-face))
    ;; Selectors.
    ;; Allow plain ":root" as a selector.
    ("^[ \t]*\\(:root\\)[\n \t]*{" (1 'css-selector keep))
    ;; FIXME: attribute selectors don't work well because they may contain
    ;; strings which have already been highlighted as f-l-string-face and
    ;; thus prevent this highlighting from being applied (actually now that
    ;; I use `keep' this should work better).  But really the part of the
    ;; selector between [...] should simply not be highlighted.
    (,(concat
       "^[ \t]*\\("
       ;; We have at least one selector.
       (css--selector-regexp sassy)
       ;; And then possibly more.
       "\\(?:"
       ;; Separators between selectors.
       "[ \n\t,+~>]+"
       (css--selector-regexp sassy)
       "\\)*"
       ;; And then a brace.
       "\\)[ \n\t]*{")
     (1 'css-selector keep))
    ;; In the above rule, we allow the open-brace to be on some subsequent
    ;; line.  This will only work if we properly mark the intervening text
    ;; as being part of a multiline element (and even then, this only
    ;; ensures proper refontification, but not proper discovery).
    ("^[ \t]*{" (0 (save-excursion
                     (goto-char (match-beginning 0))
                     (skip-chars-backward " \n\t")
                     (put-text-property (point) (match-end 0)
                                        'font-lock-multiline t)
                     ;; No face.
                     nil)))
    ;; Variables.
    (,(concat (rx symbol-start) "--" css-ident-re) (0 font-lock-variable-na=
me-face))
    ;; Properties.  Again, we don't limit ourselves to css-property-ids.
    (,(concat "\\(?:[{;]\\|^\\)[ \t]*\\("
              "\\(?:\\(" css-proprietary-nmstart-re "\\)\\|"
              css-nmstart-re "\\)" css-nmchar-re "*"
              "\\)\\s-*:")
     (1 (if (match-end 2) 'css-proprietary-property 'css-property)))
    ;; Make sure the parens in a url(...) expression receive the
    ;; default face. This is done because the parens may sometimes
    ;; receive generic string delimiter syntax (see
    ;; `css-syntax-propertize-function').
    (,css--uri-re
     (1 'default t) (2 'default t))))

(defvar css-font-lock-keywords (css--font-lock-keywords))

(defvar css-font-lock-defaults
  '(css-font-lock-keywords nil t))

(defconst css--number-regexp
  "\\(\\(?:[0-9]*\\.[0-9]+\\(?:[eE][0-9]+\\)?\\)\\|[0-9]+\\)"
  "A regular expression matching a CSS number.")

(defconst css--percent-regexp "\\([0-9]+\\)%"
  "A regular expression matching a CSS percentage.")

(defconst css--number-or-percent-regexp
  (concat "\\(?:" css--percent-regexp "\\)\\|\\(?:" css--number-regexp "\\)=
")
  "A regular expression matching a CSS number or a CSS percentage.")

(defconst css--angle-regexp
  (concat css--number-regexp
	  (regexp-opt '("deg" "grad" "rad" "turn") t)
	  "?")
  "A regular expression matching a CSS angle.")

(defun css--color-skip-blanks ()
  "Skip blanks and comments."
  (while (forward-comment 1)))

(cl-defun css--rgb-color (&optional include-alpha)
  "Parse a CSS rgb() or rgba() color.
Point should be just after the open paren.
Returns a hex RGB color, or nil if the color could not be recognized.
This recognizes CSS-color-4 extensions.
When INCLUDE-ALPHA is non-nil, the alpha component is included in
the returned hex string."
  (let ((result '())
	(iter 0))
    (while (< iter 4)
      (css--color-skip-blanks)
      (unless (looking-at css--number-or-percent-regexp)
	(cl-return-from css--rgb-color nil))
      (let* ((is-percent (match-beginning 1))
	     (str (match-string (if is-percent 1 2)))
	     (number (string-to-number str)))
	(if is-percent
	    (setq number (* 255 (/ number 100.0)))
          (when (and include-alpha (=3D iter 3))
            (setq number (* number 255))))
        (push (min (max 0 (round number)) 255) result)
	(goto-char (match-end 0))
	(css--color-skip-blanks)
	(cl-incf iter)
	;; Accept a superset of the CSS syntax since I'm feeling lazy.
	(when (and (=3D (skip-chars-forward ",/") 0)
		   (=3D iter 3))
	  ;; The alpha is optional.
	  (cl-incf iter))
	(css--color-skip-blanks)))
    (when (looking-at ")")
      (forward-char)
      (apply #'format
             (if (and include-alpha (=3D (length result) 4))
                 "#%02x%02x%02x%02x"
               "#%02x%02x%02x")
             (nreverse result)))))

(cl-defun css--hsl-color ()
  "Parse a CSS hsl() or hsla() color.
Point should be just after the open paren.
Returns a hex RGB color, or nil if the color could not be recognized.
This recognizes CSS-color-4 extensions."
  (let ((result '()))
    ;; First parse the hue.
    (css--color-skip-blanks)
    (unless (looking-at css--angle-regexp)
      (cl-return-from css--hsl-color nil))
    (let ((hue (string-to-number (match-string 1)))
	  (unit (match-string 2)))
      (goto-char (match-end 0))
      ;; Note that here "turn" is just passed through.
      (cond
       ((or (not unit) (equal unit "deg"))
	;; Degrees.
	(setq hue (/ hue 360.0)))
       ((equal unit "grad")
	(setq hue (/ hue 400.0)))
       ((equal unit "rad")
	(setq hue (/ hue (* 2 float-pi)))))
      (push (mod hue 1.0) result))
    (dotimes (_ 2)
      (skip-chars-forward ",")
      (css--color-skip-blanks)
      (unless (looking-at css--percent-regexp)
        (cl-return-from css--hsl-color nil))
      (let ((number (string-to-number (match-string 1))))
        (setq number (/ number 100.0))
        (push (min (max number 0.0) 1.0) result)
        (goto-char (match-end 0))
        (css--color-skip-blanks)))
    (css--color-skip-blanks)
    ;; Accept a superset of the CSS syntax since I'm feeling lazy.
    (when (> (skip-chars-forward ",/") 0)
      (css--color-skip-blanks)
      (unless (looking-at css--number-or-percent-regexp)
        (cl-return-from css--hsl-color nil))
      (goto-char (match-end 0))
      (css--color-skip-blanks))
    (when (looking-at ")")
      (forward-char)
      (apply #'color-rgb-to-hex
	     (nconc (apply #'color-hsl-to-rgb (nreverse result)) '(2))))))

(defconst css--colors-regexp
  (concat
   ;; Named colors.
   (regexp-opt (mapcar #'car css--color-map) 'symbols)
   "\\|"
   ;; Short hex.  css-color-4 adds alpha.
   "\\(#[[:xdigit:]]\\{3,4\\}\\b\\)"
   "\\|"
   ;; Long hex.  css-color-4 adds alpha.
   "\\(#\\(?:[[:xdigit:]][[:xdigit:]]\\)\\{3,4\\}\\b\\)"
   "\\|"
   ;; RGB.
   "\\(\\_<rgba?(\\)"
   "\\|"
   ;; HSL.
   "\\(\\_<hsla?(\\)")
  "A regular expression that matches the start of a CSS color.")

(defun css--hex-color (str)
  "Convert a CSS hex color to an Emacs hex color.
STR is the incoming CSS hex color.
This function simply drops any transparency."
  ;; Either #RGB or #RRGGBB, drop the "A" or "AA".
  (substring str 0 (if (> (length str) 5) 7 4)))

(defun css--hex-alpha (hex)
  "Return the alpha component of CSS color HEX.
HEX can either be in the #RGBA or #RRGGBBAA format.  Return nil
if the color doesn't have an alpha component."
  (cl-case (length hex)
    (5 (string (elt hex 4)))
    (9 (substring hex 7 9))))

(defun css--named-color (start-point str)
  "Check whether STR, seen at point, is CSS named color.
Returns STR if it is a valid color.  Special care is taken
to exclude some SCSS constructs."
  (when-let* ((color (assoc str css--color-map)))
    (save-excursion
      (goto-char start-point)
      (forward-comment (- (point)))
      (skip-chars-backward "@[:alpha:]")
      (unless (looking-at-p "@\\(mixin\\|include\\)")
        (cdr color)))))

(defun css--compute-color (start-point match)
  "Return the CSS color at point.
Point should be just after the start of a CSS color, as recognized
by `css--colors-regexp'.  START-POINT is the start of the color,
and MATCH is the string matched by the regexp.

This function will either return the color, as a hex RGB string;
or nil if no color could be recognized.  When this function
returns, point will be at the end of the recognized color."
  (cond
   ((eq (aref match 0) ?#)
    (css--hex-color match))
   ((member match '("rgb(" "rgba("))
    (css--rgb-color))
   ((member match '("hsl(" "hsla("))
    (css--hsl-color))
   ;; Evaluate to the color if the name is found.
   ((css--named-color start-point match))))

(defcustom css-fontify-colors t
  "Whether CSS colors should be fontified using the color as the background.
When non-nil, a text representing CSS color will be fontified
such that its background is the color itself.  E.g., #ff0000 will
be fontified with a red background."
  :version "26.1"
  :group 'css
  :type 'boolean
  :safe 'booleanp)

(defun css--fontify-region (start end &optional loudly)
  "Fontify a CSS buffer between START and END.
START and END are buffer positions."
  (let ((extended-region (font-lock-default-fontify-region start end loudly=
)))
    (when css-fontify-colors
      (when (and (consp extended-region)
		 (eq (car extended-region) 'jit-lock-bounds))
	(setq start (cadr extended-region))
	(setq end (cddr extended-region)))
      (save-excursion
	(let ((case-fold-search t))
	  (goto-char start)
	  (while (re-search-forward css--colors-regexp end t)
	    ;; Skip comments and strings.
	    (unless (nth 8 (syntax-ppss))
	      (let* ((start (match-beginning 0))
                     (color (css--compute-color start (match-string 0))))
		(when color
		  (with-silent-modifications
		    ;; Use the color as the background, to make it more
		    ;; clear.  Use a contrasting color as the foreground,
		    ;; to make it readable.  Finally, have a small box
		    ;; using the existing foreground color, to make sure
		    ;; it stands out a bit from any other text; in
		    ;; particular this is nice when the color matches the
		    ;; buffer's background color.
		    (add-text-properties
		     start (point)
		     (list 'face (list :background color
				       :foreground (readable-foreground-color
                                                    color)
				       :box '(:line-width -1))))))))))))
    extended-region))

(defcustom css-indent-offset 4
  "Basic size of one indentation step."
  :version "22.2"
  :type 'integer
  :safe 'integerp)

(defconst css-smie-grammar
  (smie-prec2->grammar
   (smie-precs->prec2
    '((assoc ";")
      ;; Colons that belong to a CSS property.  These get a higher
      ;; precedence than other colons, such as colons in selectors,
      ;; which are represented by a plain ":" token.
      (left ":-property")
      (assoc ",")
      (assoc ":")))))

(defun css--colon-inside-selector-p ()
  "Return t if point looks to be inside a CSS selector.
This function is intended to be good enough to help SMIE during
tokenization, but should not be regarded as a reliable function
for determining whether point is within a selector."
  (save-excursion
    (re-search-forward "[{};]" nil t)
    (eq (char-before) ?\{)))

(defun css--colon-inside-funcall ()
  "Return t if point is inside a function call."
  (when-let* ((opening-paren-pos (nth 1 (syntax-ppss))))
    (save-excursion
      (goto-char opening-paren-pos)
      (eq (char-after) ?\())))

(defun css-smie--forward-token ()
  (cond
   ((and (eq (char-before) ?\})
         (scss-smie--not-interpolation-p)
         ;; FIXME: If the next char is not whitespace, what should we do?
         (or (memq (char-after) '(?\s ?\t ?\n))
             (looking-at comment-start-skip)))
    (if (memq (char-after) '(?\s ?\t ?\n))
        (forward-char 1) (forward-comment 1))
    ";")
   ((progn (forward-comment (point-max))
           (looking-at "[;,:]"))
    (forward-char 1)
    (if (equal (match-string 0) ":")
        (if (or (css--colon-inside-selector-p)
                (css--colon-inside-funcall))
            ":"
          ":-property")
      (match-string 0)))
   (t (smie-default-forward-token))))

(defun css-smie--backward-token ()
  (let ((pos (point)))
    (forward-comment (- (point)))
    (cond
     ;; FIXME: If the next char is not whitespace, what should we do?
     ((and (eq (char-before) ?\}) (scss-smie--not-interpolation-p)
           (> pos (point))) ";")
     ((memq (char-before) '(?\; ?\, ?\:))
      (forward-char -1)
      (if (eq (char-after) ?\:)
          (if (or (css--colon-inside-selector-p)
                  (css--colon-inside-funcall))
              ":"
            ":-property")
        (string (char-after))))
     (t (smie-default-backward-token)))))

(defun css-smie-rules (kind token)
  (pcase (cons kind token)
    ('(:elem . basic) css-indent-offset)
    ('(:elem . arg) 0)
    ;; "" stands for BOB (bug#15467).
    (`(:list-intro . ,(or ";" "" ":-property")) t)
    ('(:before . "{")
     (when (or (smie-rule-hanging-p) (smie-rule-bolp))
       (smie-backward-sexp ";")
       (unless (eq (char-after) ?\{)
         (smie-indent-virtual))))
    ('(:before . "(")
     (cond
      ((smie-rule-hanging-p) (smie-rule-parent 0))
      ((not (smie-rule-bolp)) 0)))
    ('(:after . ":-property")
     (when (smie-rule-hanging-p)
       css-indent-offset))))

;;; Tree-sitter

(defvar css-ts-mode-map (copy-keymap css-mode-map)
  "Keymap used in `css-ts-mode'.")

(defvar css--treesit-indent-rules
  '((css
     ((node-is "}") parent-bol 0)
     ((node-is ")") parent-bol 0)
     ((node-is "]") parent-bol 0)

     ((parent-is "block") parent-bol css-indent-offset)
     ((parent-is "arguments") parent-bol css-indent-offset)
     ((match nil "declaration" nil 0 3) parent-bol css-indent-offset)
     ((match nil "declaration" nil 3) (nth-sibling 2) 0)))
  "Tree-sitter indentation rules for `css-ts-mode'.")

(defvar css--treesit-settings
  (treesit-font-lock-rules
   :feature 'comment
   :language 'css
   '((comment) @font-lock-comment-face)

   :feature 'string
   :language 'css
   '((string_value) @font-lock-string-face)

   :feature 'keyword
   :language 'css
   '(["@media"
      "@import"
      "@charset"
      "@namespace"
      "@keyframes"] @font-lock-builtin-face
      ["and"
       "or"
       "not"
       "only"
       "selector"] @font-lock-keyword-face)

   :feature 'variable
   :language 'css
   '((plain_value) @font-lock-variable-name-face)

   :language 'css
   :feature 'operator
   `(["=3D" "~=3D" "^=3D" "|=3D" "*=3D" "$=3D"] @font-lock-operator-face)

   :feature 'selector
   :language 'css
   '((class_selector) @css-selector
     (child_selector) @css-selector
     (id_selector) @css-selector
     (tag_name) @css-selector
     (class_name) @css-selector)

   :feature 'property
   :language 'css
   `((property_name) @css-property)

   :feature 'function
   :language 'css
   '((function_name) @font-lock-function-name-face)

   :feature 'constant
   :language 'css
   '((integer_value) @font-lock-number-face
     (float_value) @font-lock-number-face
     (unit) @font-lock-constant-face
     (important) @font-lock-builtin-face)

   :feature 'query
   :language 'css
   '((keyword_query) @font-lock-property-use-face
     (feature_name) @font-lock-property-use-face)

   :feature 'bracket
   :language 'css
   '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face)

   :feature 'error
   :language 'css
   '((ERROR) @error))
  "Tree-sitter font-lock settings for `css-ts-mode'.")

(defun css--treesit-defun-name (node)
  "Return the defun name of NODE.
Return nil if there is no name or if NODE is not a defun node."
  (pcase (treesit-node-type node)
    ("rule_set" (treesit-node-text
                 (treesit-node-child node 0) t))
    ("media_statement"
     (let ((block (treesit-node-child node -1)))
       (string-trim
        (buffer-substring-no-properties
         (treesit-node-start node)
         (treesit-node-start block)))))))

;;; Completion

(defun css--complete-property ()
  "Complete property at point."
  (save-excursion
    (let ((pos (point)))
      (skip-chars-backward "-[:alnum:]")
      (let ((start (point)))
        (skip-chars-backward " \t\r\n")
        (when (memq (char-before) '(?\{ ?\;))
          (list start pos css-property-ids))))))

(defun css--complete-bang-rule ()
  "Complete bang-rule at point."
  (save-excursion
    (let ((pos (point)))
      (skip-chars-backward "-[:alnum:]")
      (when (eq (char-before) ?\!)
        (list (point) pos css--bang-ids)))))

(defun css--complete-pseudo-element-or-class ()
  "Complete pseudo-element or pseudo-class at point."
  (save-excursion
    (let ((pos (point)))
      (skip-chars-backward "-[:alnum:]")
      (when (eq (char-before) ?\:)
        (let ((double-colon (eq (char-before (- (point) 1)) ?\:)))
          (list (- (point) (if double-colon 2 1))
                pos
                (nconc
                 (unless double-colon
                   (mapcar (lambda (id) (concat ":" id)) css-pseudo-class-i=
ds))
                 (mapcar (lambda (id) (concat "::" id)) css-pseudo-element-=
ids))
                :company-kind (lambda (_) 'function)))))))

(defun css--complete-at-rule ()
  "Complete at-rule (statement beginning with `@') at point."
  (save-excursion
    (let ((pos (point)))
      (skip-chars-backward "-[:alnum:]")
      (when (eq (char-before) ?\@)
        (list (point) pos css--at-ids
              :company-kind (lambda (_) 'keyword))))))

(defvar css--property-value-cache
  (make-hash-table :test 'equal :size (length css-property-alist))
  "Cache of previously completed property values.")

(defun css--value-class-lookup (value-class)
  "Return a list of value completion candidates for VALUE-CLASS.
Completion candidates are looked up in `css-value-class-alist' by
the symbol VALUE-CLASS."
  (seq-uniq
   (seq-mapcat
    (lambda (value)
      (if (stringp value)
          (list value)
        (css--value-class-lookup value)))
    (cdr (assq value-class css-value-class-alist)))))

(defun css--property-values (property)
  "Return a list of value completion candidates for PROPERTY.
Completion candidates are looked up in `css-property-alist' by
the string PROPERTY."
  (or (gethash property css--property-value-cache)
      (let ((values
             (seq-uniq
              (seq-mapcat
               (lambda (value)
                 (if (stringp value)
                     (list value)
                   (or (css--value-class-lookup value)
                       (css--property-values (symbol-name value)))))
               (cdr (assoc property css-property-alist))))))
        (puthash property values css--property-value-cache))))

(defun css--complete-property-value ()
  "Complete property value at point."
  (let ((property (and (looking-back "\\([[:alnum:]-]+\\):[^/][^;]*"
                                     (or (ppss-innermost-start (syntax-ppss=
))
                                         (point))
                                     t)
                       (member (match-string-no-properties 1)
                               css-property-ids))))
    (when property
      (let ((end (point)))
        (save-excursion
          (skip-chars-backward "[:graph:]")
          (list (point) end
                (append '("inherit" "initial" "unset")
                        (css--property-values (car property)))
                :company-kind (lambda (_) 'value)))))))

(defvar css--html-tags (mapcar #'car html-tag-alist)
  "List of HTML tags.
Used to provide completion of HTML tags in selectors.")

(defvar-local css--nested-selectors-allowed nil
  "Non-nil if nested selectors are allowed in the current mode.")

(defvar css-class-list-function #'ignore
  "Called to provide completions of class names.
This can be bound by buffers that are able to suggest class name
completions, such as HTML mode buffers.")

(defvar css-id-list-function #'ignore
  "Called to provide completions of IDs.
This can be bound by buffers that are able to suggest ID
completions, such as HTML mode buffers.")

(defun css--foreign-completions (extractor)
  "Return a list of completions provided by other buffers.
EXTRACTOR should be the name of a function that may be defined in
one or more buffers.  In each of the buffers where EXTRACTOR is
defined, EXTRACTOR is called and the results are accumulated into
a list of completions."
  (delete-dups
   (seq-mapcat
    (lambda (buf)
      (with-current-buffer buf
        (funcall (symbol-value extractor))))
    (buffer-list))))

(defun css--complete-selector ()
  "Complete part of a CSS selector at point."
  (when (or (=3D (nth 0 (syntax-ppss)) 0) css--nested-selectors-allowed)
    (let ((end (point)))
      (save-excursion
        (skip-chars-backward "-[:alnum:]")
        (let ((start-char (char-before)))
          (list
           (point) end
           (completion-table-dynamic
            (lambda (_)
              (cond
               ((eq start-char ?.)
                (css--foreign-completions 'css-class-list-function))
               ((eq start-char ?#)
                (css--foreign-completions 'css-id-list-function))
               (t css--html-tags))))))))))

(defun css-completion-at-point ()
  "Complete current symbol at point.
Currently supports completion of CSS properties, property values,
pseudo-elements, pseudo-classes, at-rules, bang-rules, and HTML
tags, classes and IDs."
  (or (css--complete-bang-rule)
      (css--complete-property-value)
      (css--complete-pseudo-element-or-class)
      (css--complete-at-rule)
      (seq-let (prop-beg prop-end prop-table) (css--complete-property)
        (seq-let (sel-beg sel-end sel-table) (css--complete-selector)
          (when (or prop-table sel-table)
            ;; FIXME: If both prop-table and sel-table are set but
            ;; prop-beg/prop-end is different from sel-beg/sel-end
            ;; we have a problem!
            `(,@(if prop-table
                    (list prop-beg prop-end)
                  (list sel-beg sel-end))
              ,(completion-table-merge prop-table sel-table)
              :company-kind
              ,(lambda (s) (if (test-completion s prop-table) 'property 'ke=
yword))
              :exit-function
              ,(lambda (string status)
                 (and (eq status 'finished)
                      (eolp)
                      prop-table
                      (test-completion string prop-table)
                      (not (and sel-table
                                (test-completion string sel-table)))
                      (progn (insert ": ;")
                             (forward-char -1))))))))))

(defun css--color-to-4-dpc (hex)
  "Convert the CSS color HEX to four digits per component.
CSS colors use one or two digits per component for RGB hex
values.  Convert the given color to four digits per component.

Note that this function handles CSS colors specifically, and
should not be mixed with those in color.el."
  (let ((six-digits (=3D (length hex) 7)))
    (apply
     #'concat
     `("#"
       ,@(seq-mapcat
          (apply-partially #'make-list (if six-digits 2 4))
          (seq-partition (seq-drop hex 1) (if six-digits 2 1)))))))

(defun css--format-hex (hex)
  "Format a CSS hex color by shortening it if possible."
  (let ((parts (seq-partition (seq-drop hex 1) 2)))
    (if (and (>=3D (length hex) 6)
             (seq-every-p (lambda (p) (eq (elt p 0) (elt p 1))) parts))
        (apply #'string
               (cons ?# (mapcar (lambda (p) (elt p 0)) parts)))
      hex)))

(defun css--named-color-to-hex ()
  "Convert named CSS color at point to hex format.
Return non-nil if a conversion was made.

Note that this function handles CSS colors specifically, and
should not be mixed with those in color.el."
  (save-excursion
    (unless (or (looking-at css--colors-regexp)
                (eq (char-before) ?#))
      (backward-word))
    (when (member (word-at-point) (mapcar #'car css--color-map))
      (looking-at css--colors-regexp)
      (let ((color (css--compute-color (point) (match-string 0))))
        (replace-match (css--format-hex color)))
      t)))

(defun css--format-rgba-alpha (alpha)
  "Return ALPHA component formatted for use in rgba()."
  (let ((a (string-to-number (format "%.2f" alpha))))
    (if (or (=3D a 0)
            (=3D a 1))
        (format "%d" a)
      (string-remove-suffix "0" (number-to-string a)))))

(defun css--hex-to-rgb ()
  "Convert CSS hex color at point to RGB format.
Return non-nil if a conversion was made.

Note that this function handles CSS colors specifically, and
should not be mixed with those in color.el."
  (save-excursion
    (unless (or (eq (char-after) ?#)
                (eq (char-before) ?\())
      (backward-sexp))
    (when-let* ((hex (when (looking-at css--colors-regexp)
                       (and (eq (elt (match-string 0) 0) ?#)
                            (match-string 0))))
                (rgb (css--hex-color hex)))
      (seq-let (r g b)
          (mapcar (lambda (x) (round (* x 255)))
                  (color-name-to-rgb (css--color-to-4-dpc rgb)))
        (replace-match
         (if-let* ((alpha (css--hex-alpha hex))
                   (a (css--format-rgba-alpha
                       (/ (string-to-number alpha 16)
                          (float (- (expt 16 (length alpha)) 1))))))
             (format "rgba(%d, %d, %d, %s)" r g b a)
           (format "rgb(%d, %d, %d)" r g b))
         t))
      t)))

(defun css--rgb-to-named-color-or-hex ()
  "Convert CSS RGB color at point to a named color or hex format.
Convert to a named color if the color at point has a name, else
convert to hex format.  Return non-nil if a conversion was made.

Note that this function handles CSS colors specifically, and
should not be mixed with those in color.el."
  (save-excursion
    (when-let* ((open-paren-pos (nth 1 (syntax-ppss))))
      (when (save-excursion
              (goto-char open-paren-pos)
              (looking-back "rgba?" (- (point) 4)))
        (goto-char (nth 1 (syntax-ppss)))))
    (when (eq (char-before) ?\))
      (backward-sexp))
    (skip-chars-backward "rgba")
    (when (looking-at css--colors-regexp)
      (let* ((start (match-end 0))
             (color (save-excursion
                      (goto-char start)
                      (css--rgb-color t))))
        (when color
          (kill-sexp)
          (kill-sexp)
          (let ((named-color (seq-find (lambda (x) (equal (cdr x) color))
                                       css--color-map)))
            (insert (if named-color
                        (car named-color)
                      (css--format-hex color))))
          t)))))

(defun css-cycle-color-format ()
  "Cycle the color at point between different CSS color formats.
Supported formats are by name (if possible), hexadecimal, and
rgb()/rgba()."
  (interactive)
  (or (css--named-color-to-hex)
      (css--hex-to-rgb)
      (css--rgb-to-named-color-or-hex)
      (message "It doesn't look like a color at point")))

(defun css--join-nested-selectors (selectors)
  "Join a list of nested CSS selectors."
  (let ((processed '())
        (prev nil))
    (dolist (sel selectors)
      (cond
       ((seq-contains-p sel ?&)
        (setq sel (replace-regexp-in-string "&" prev sel))
        (pop processed))
       ;; Unless this is the first selector, separate this one and the
       ;; previous one by a space.
       (processed
        (push " " processed)))
      (push sel processed)
      (setq prev sel))
    (apply #'concat (nreverse processed))))

(defun css--prev-index-position ()
  (when (nth 7 (syntax-ppss))
    (goto-char (comment-beginning)))
  (forward-comment (- (point)))
  (when (search-backward "{" (point-min) t)
    (if (re-search-backward "}\\|;\\|{" (point-min) t)
        (forward-char)
      (goto-char (point-min)))
    (forward-comment (point-max))
    (save-excursion (re-search-forward "[^{;]*"))))

(defun css--extract-index-name ()
  (save-excursion
    (let ((res (list (match-string-no-properties 0))))
      (condition-case nil
          (while t
            (goto-char (nth 1 (syntax-ppss)))
            (if (re-search-backward "}\\|;\\|{" (point-min) t)
                (forward-char)
              (goto-char (point-min)))
            (forward-comment (point-max))
            (when (save-excursion
                    (re-search-forward "[^{;]*"))
              (push (match-string-no-properties 0) res)))
        (error
         (css--join-nested-selectors
          (mapcar
           (lambda (s)
             (string-trim
              (replace-regexp-in-string "[\n ]+" " " s)))
           res)))))))

(defvar css--treesit-font-lock-feature-list
  '((selector comment query keyword)
    (property constant string)
    (error variable function operator bracket))
  "Settings for `treesit-font-lock-feature-list'.")

(defvar css--treesit-simple-imenu-settings
  `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
      nil nil))
  "Settings for `treesit-simple-imenu'.")

(defvar css--treesit-defun-type-regexp
  "rule_set"
  "Settings for `treesit-defun-type-regexp'.")

(define-derived-mode css-base-mode prog-mode "CSS"
  "Generic mode to edit Cascading Style Sheets (CSS).

This is a generic major mode intended to be inherited by a
concrete implementation.  Currently there are two concrete
implementations: `css-mode' and `css-ts-mode'."
  (setq-local comment-start "/*")
  (setq-local comment-start-skip "/\\*+[ \t]*")
  (setq-local comment-end "*/")
  (setq-local comment-end-skip "[ \t]*\\*+/")
  (setq-local electric-indent-chars
              (append css-electric-keys electric-indent-chars))
  ;; The default "." creates ambiguity with class selectors.
  (setq-local imenu-space-replacement " "))

;;;###autoload
(define-derived-mode css-ts-mode css-base-mode "CSS"
  "Major mode to edit Cascading Style Sheets (CSS).
\\<css-ts-mode-map>

This mode provides syntax highlighting, indentation, completion,
and documentation lookup for CSS, based on the tree-sitter
library.

Use `\\[completion-at-point]' to complete CSS properties,
property values, pseudo-elements, pseudo-classes, at-rules,
bang-rules, and HTML tags, classes and IDs.  Completion
candidates for HTML class names and IDs are found by looking
through open HTML mode buffers.

Use `\\[info-lookup-symbol]' to look up documentation of CSS
properties, at-rules, pseudo-classes, and pseudo-elements on the
Mozilla Developer Network (MDN).

Use `\\[fill-paragraph]' to reformat CSS declaration blocks.  It
can also be used to fill comments.

\\{css-mode-map}"
  :syntax-table css-mode-syntax-table
  (when (treesit-ready-p 'css)
    ;; Borrowed from `css-mode'.
    (setq-local syntax-propertize-function
                css-syntax-propertize-function)
    (add-hook 'completion-at-point-functions
              #'css-completion-at-point nil 'local)
    (setq-local fill-paragraph-function #'css-fill-paragraph)
    (setq-local adaptive-fill-function #'css-adaptive-fill)
    ;; `css--fontify-region' first calls the default function, which
    ;; will call tree-sitter's function, then it fontifies colors.
    (setq-local font-lock-fontify-region-function #'css--fontify-region)

    ;; Tree-sitter specific setup.
    (setq treesit-primary-parser (treesit-parser-create 'css))
    (setq-local treesit-simple-indent-rules css--treesit-indent-rules)
    (setq-local treesit-defun-type-regexp css--treesit-defun-type-regexp)
    (setq-local treesit-defun-name-function #'css--treesit-defun-name)
    (setq-local treesit-font-lock-settings css--treesit-settings)
    (setq-local treesit-font-lock-feature-list css--treesit-font-lock-featu=
re-list)
    (setq-local treesit-simple-imenu-settings css--treesit-simple-imenu-set=
tings)

    (treesit-major-mode-setup)

    (add-to-list 'auto-mode-alist '("\\.css\\'" . css-ts-mode))))

(derived-mode-add-parents 'css-ts-mode '(css-mode))

;;;###autoload
(define-derived-mode css-mode css-base-mode "CSS"
  "Major mode to edit Cascading Style Sheets (CSS).
\\<css-mode-map>
This mode provides syntax highlighting, indentation, completion,
and documentation lookup for CSS.

Use `\\[completion-at-point]' to complete CSS properties, property values,
pseudo-elements, pseudo-classes, at-rules, bang-rules, and HTML
tags, classes and IDs.  Completion candidates for HTML class
names and IDs are found by looking through open HTML mode
buffers.

Use `\\[info-lookup-symbol]' to look up documentation of CSS properties, at=
=2Drules,
pseudo-classes, and pseudo-elements on the Mozilla Developer
Network (MDN).

Use `\\[fill-paragraph]' to reformat CSS declaration blocks.  It can also
be used to fill comments.

\\{css-mode-map}"
  (setq-local font-lock-defaults css-font-lock-defaults)
  (setq-local syntax-propertize-function
              css-syntax-propertize-function)
  (setq-local fill-paragraph-function #'css-fill-paragraph)
  (setq-local adaptive-fill-function #'css-adaptive-fill)
  (setq-local add-log-current-defun-function #'css-current-defun-name)
  (smie-setup css-smie-grammar #'css-smie-rules
              :forward-token #'css-smie--forward-token
              :backward-token #'css-smie--backward-token)
  (setq-local font-lock-fontify-region-function #'css--fontify-region)
  (add-hook 'completion-at-point-functions
            #'css-completion-at-point nil 'local)
  (setq-local imenu-prev-index-position-function
              #'css--prev-index-position)
  (setq-local imenu-extract-index-name-function
              #'css--extract-index-name))

(defvar comment-continue)

(defun css-fill-paragraph (&optional justify)
  (save-excursion
    ;; Fill succeeding comment when invoked right before a multi-line
    ;; comment.
    (when (save-excursion
            (beginning-of-line)
            (comment-search-forward (line-end-position) t))
      (goto-char (match-end 0)))
    (let ((ppss (syntax-ppss))
          (eol (line-end-position)))
      (cond
       ((and (nth 4 ppss)
             (save-excursion
               (goto-char (nth 8 ppss))
               (forward-comment 1)
               (prog1 (not (bolp))
                 (setq eol (point)))))
        ;; Filling inside a comment whose comment-end marker is not \n.
        ;; This code is meant to be generic, so that it works not only for
        ;; css-mode but for all modes.
        (save-restriction
          (narrow-to-region (nth 8 ppss) eol)
          (comment-normalize-vars)      ;Will define comment-continue.
          (let ((fill-paragraph-function nil)
                (paragraph-separate
                 (if (and comment-continue
                          (string-match "[^ \t]" comment-continue))
                     (concat "\\(?:[ \t]*\\(?:"
                             (regexp-quote comment-continue) "\\|"
                             comment-start-skip "\\|"
                             comment-end-skip "\\)\\)?"
                             "\\(?:" paragraph-separate "\\)")
                   paragraph-separate))
                (paragraph-start
                 (if (and comment-continue
                          (string-match "[^ \t]" comment-continue))
                     (concat "\\(?:[ \t]*" (regexp-quote comment-continue)
                             "\\)?\\(?:" paragraph-start "\\)")
                   paragraph-start)))
            (fill-paragraph justify)
            ;; Don't try filling again.
            t)))

       ((and (null (nth 8 ppss))
             (or (nth 1 ppss)
                 (and (ignore-errors
                        (down-list 1)
                        (when (<=3D (point) eol)
                          (setq ppss (syntax-ppss)))))))
        (goto-char (nth 1 ppss))
        (let ((end (save-excursion
                     (ignore-errors (forward-sexp 1) (copy-marker (point) t=
)))))
          (when end
            (while (re-search-forward "[{;}]" end t)
              (cond
               ;; This is a false positive inside a string or comment.
               ((nth 8 (syntax-ppss)) nil)
               ;; This is a false positive when encountering an
               ;; interpolated variable (bug#19751).
               ((eq (char-before (- (point) 1)) ?#) nil)
               ((eq (char-before) ?\})
                (save-excursion
                  (forward-char -1)
                  (skip-chars-backward " \t")
                  (when (and (not (bolp))
                             (scss-smie--not-interpolation-p))
                    (newline))))
               (t
                (while
                    (progn
                      (setq eol (line-end-position))
                      (and (forward-comment 1)
                           (> (point) eol)
                           ;; A multi-line comment should be on its own lin=
e.
                           (save-excursion (forward-comment -1)
                                           (when (< (point) eol)
                                             (newline)
                                             t)))))
                (if (< (point) eol) (newline)))))
            (goto-char (nth 1 ppss))
            (indent-region (line-beginning-position 2) end)
            ;; Don't use the default filling code.
            t)))))))

(defun css-adaptive-fill ()
  (when (looking-at "[ \t]*/\\*[ \t]*")
    (let ((str (match-string 0)))
      (and (string-match "/\\*" str)
           (replace-match " *" t t str)))))

(defun css-current-defun-name ()
  "Return the name of the CSS section at point, or nil."
  (save-excursion
    (when (css--prev-index-position)
      (css--extract-index-name))))

;;; SCSS mode

(defvar scss-mode-syntax-table
  (let ((st (make-syntax-table css-mode-syntax-table)))
    (modify-syntax-entry ?/ ". 124" st)
    (modify-syntax-entry ?\n ">" st)
    ;; Variable names are prefixed by $.
    (modify-syntax-entry ?$ "_" st)
    (modify-syntax-entry ?% "_" st)
    st))

(defun scss-font-lock-keywords ()
  (append `((,(concat "$" css-ident-re) (0 font-lock-variable-name-face)))
          (css--font-lock-keywords 'sassy)
          `((,(concat "@mixin[ \t]+\\(" css-ident-re "\\)[ \t]*(")
             (1 font-lock-function-name-face)))))

(defun scss-smie--not-interpolation-p ()
  (save-excursion
    (forward-char -1)
    (or (zerop (skip-chars-backward "-[:alnum:]"))
        (not (looking-back "#{\\$" (- (point) 3))))))

;;;###autoload (add-to-list 'auto-mode-alist '("\\.scss\\'" . scss-mode))
;;;###autoload
(define-derived-mode scss-mode css-mode "SCSS"
  "Major mode to edit \"Sassy CSS\" files."
  (setq-local comment-start "// ")
  (setq-local comment-end "")
  (setq-local comment-continue " *")
  (setq-local comment-start-skip "/[*/]+[ \t]*")
  (setq-local comment-end-skip "[ \t]*\\(?:\n\\|\\*+/\\)")
  (setq-local css--at-ids (append css-at-ids scss-at-ids))
  (setq-local css--bang-ids (append css-bang-ids scss-bang-ids))
  (setq-local css--nested-selectors-allowed t)
  (setq-local font-lock-defaults
              (list (scss-font-lock-keywords) nil t)))

=0C

(defvar css--mdn-lookup-history nil)

(defcustom css-lookup-url-format
  "https://developer.mozilla.org/en-US/docs/Web/CSS/%s?raw&macros"
  "Format for a URL where CSS documentation can be found.
The format should include a single \"%s\" substitution.
The name of the CSS property, @-id, pseudo-class, or pseudo-element
to look up will be substituted there."
  :version "26.1"
  :type 'string
  :group 'css)

(defun css--mdn-after-render ()
  (setf header-line-format nil)
  (goto-char (point-min))
  (let ((window (get-buffer-window (current-buffer) 'visible)))
    (when window
      (when (re-search-forward "^\\(Summary\\|Syntax\\)" nil 'move)
        (beginning-of-line)
        (set-window-start window (point))))))

(defconst css--mdn-symbol-regexp
  (concat "\\("
	  ;; @-ids.
	  "\\(@" (regexp-opt css-at-ids) "\\)"
	  "\\|"
	  ;; ;; Known properties.
	  (regexp-opt css-property-ids t)
	  "\\|"
	  ;; Pseudo-classes.
	  "\\(:" (regexp-opt css-pseudo-class-ids) "\\)"
	  "\\|"
	  ;; Pseudo-elements with either one or two ":"s.
	  "\\(::?" (regexp-opt css-pseudo-element-ids) "\\)"
	  "\\)")
  "Regular expression to match the CSS symbol at point.")

(defconst css--mdn-property-regexp
  (concat "\\_<" (regexp-opt css-property-ids t) "\\s-*\\(?:\\=3D\\|:\\)")
  "Regular expression to match a CSS property.")

(defconst css--mdn-completion-list
  (nconc
   ;; @-ids.
   (mapcar (lambda (atrule) (concat "@" atrule)) css-at-ids)
   ;; Pseudo-classes.
   (mapcar (lambda (class) (concat ":" class)) css-pseudo-class-ids)
   ;; Pseudo-elements with either one or two ":"s.
   (mapcar (lambda (elt) (concat ":" elt)) css-pseudo-element-ids)
   (mapcar (lambda (elt) (concat "::" elt)) css-pseudo-element-ids)
   ;; Properties.
   css-property-ids)
  "List of all symbols available for lookup via MDN.")

(defun css--mdn-find-symbol ()
  "A helper for `css-lookup-symbol' that finds the symbol at point.
Returns the symbol, a string, or nil if none found."
  (save-excursion
    ;; Skip any whitespace between the word and point.
    (skip-chars-backward "- \t")
    ;; Skip backward over a word.
    (skip-chars-backward "-[:alnum:]")
    ;; Now skip ":" or "@" to see if it's a pseudo-element or at-id.
    (skip-chars-backward "@:")
    (if (looking-at css--mdn-symbol-regexp)
	(match-string-no-properties 0)
      (let ((bound (save-excursion
		     (beginning-of-line)
		     (point))))
	(when (re-search-backward css--mdn-property-regexp bound t)
	  (match-string-no-properties 1))))))

;;;###autoload
(defun css-lookup-symbol (symbol)
  "Display the CSS documentation for SYMBOL, as found on MDN.
When this command is used interactively, it picks a default
symbol based on the CSS text before point -- either an @-keyword,
a property name, a pseudo-class, or a pseudo-element, depending
on what is seen near point."
  (interactive
   (list
    (let* ((sym (css--mdn-find-symbol))
	   (enable-recursive-minibuffers t)
	   (value (completing-read (format-prompt "Describe CSS symbol" sym)
		                   css--mdn-completion-list nil nil nil
		                   'css--mdn-lookup-history sym)))
      (if (equal value "") sym value))))
  (when symbol
    ;; If we see a single-colon pseudo-element like ":after", turn it
    ;; into "::after".
    (when (and (eq (aref symbol 0) ?:)
	       (member (substring symbol 1) css-pseudo-element-ids))
      (setq symbol (concat ":" symbol)))
    (let ((url (format css-lookup-url-format symbol))
          (buffer (get-buffer-create "*MDN CSS*")))
      ;; Make sure to display the buffer before calling `eww', as that
      ;; calls `pop-to-buffer-same-window'.
      (switch-to-buffer-other-window buffer)
      (with-current-buffer buffer
        (eww-mode)
        (add-hook 'eww-after-render-hook #'css--mdn-after-render nil t)
        (eww url)))))

(provide 'css-mode)
;;; css-mode.el ends here

--nextPart2576831.XAFRqVoOGU
Content-Disposition: attachment; filename="html-ts-mode.el"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-emacs-lisp; charset="UTF-8"; name="html-ts-mode.el"

;;; html-ts-mode.el --- tree-sitter support for HTML  -*- lexical-binding: t; -*-

;; Copyright (C) 2023-2025 Free Software Foundation, Inc.

;; Author     : Theodor Thornhill <theo@HIDDEN>
;; Maintainer : Theodor Thornhill <theo@HIDDEN>
;; Created    : January 2023
;; Keywords   : html languages tree-sitter

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.

;;; Tree-sitter language versions
;;
;; html-ts-mode is known to work with the following languages and version:
;; - tree-sitter-html: v0.23.2-1-gd9219ad
;;
;; We try our best to make builtin modes work with latest grammar
;; versions, so a more recent grammar version has a good chance to work.
;; Send us a bug report if it doesn't.

;;; Commentary:
;;

;;; Code:

(require 'treesit)
(require 'sgml-mode)

(declare-function treesit-parser-create "treesit.c")
(declare-function treesit-node-type "treesit.c")

(defcustom html-ts-mode-indent-offset 2
  "Number of spaces for each indentation step in `html-ts-mode'."
  :version "29.1"
  :type 'integer
  :safe 'integerp
  :group 'html)

(defvar html-ts-mode--indent-rules
  `((html
     ((parent-is "fragment") column-0 0)
     ((node-is "/>") parent-bol 0)
     ((node-is ">") parent-bol 0)
     ((node-is "end_tag") parent-bol 0)
     ((parent-is "comment") prev-adaptive-prefix 0)
     ((parent-is "element") parent-bol html-ts-mode-indent-offset)
     ((parent-is "script_element") parent-bol html-ts-mode-indent-offset)
     ((parent-is "style_element") parent-bol html-ts-mode-indent-offset)
     ((parent-is "start_tag") parent-bol html-ts-mode-indent-offset)
     ((parent-is "self_closing_tag") parent-bol html-ts-mode-indent-offset)))
  "Tree-sitter indent rules.")

(defvar html-ts-mode--font-lock-settings
  (treesit-font-lock-rules
   :language 'html
   :override t
   :feature 'comment
   `((comment) @font-lock-comment-face)
   :language 'html
   :override t
   :feature 'keyword
   `("doctype" @font-lock-keyword-face)
   :language 'html
   :override t
   :feature 'definition
   `((tag_name) @font-lock-function-name-face)
   :language 'html
   :override t
   :feature 'string
   `((quoted_attribute_value) @font-lock-string-face)
   :language 'html
   :override t
   :feature 'property
   `((attribute_name) @font-lock-variable-name-face))
  "Tree-sitter font-lock settings for `html-ts-mode'.")

(defvar html-ts-mode--treesit-things-settings
  `((html
     (sexp ,(regexp-opt '("element"
                          "text"
                          "attribute"
                          "value")))
     (sexp-list ,(regexp-opt '("element")) 'symbols)
     (sentence "tag")
     ;; (sentence ,(regex-opt '("start_tag" "end_tag")))
     (text ,(regexp-opt '("comment" "text")))))
  "Settings for `treesit-thing-settings'.")

(defvar html-ts-mode--treesit-font-lock-feature-list
  '((comment keyword definition)
    (property string)
    () ())
  "Settings for `treesit-font-lock-feature-list'.")

(defvar html-ts-mode--treesit-simple-imenu-settings
  '(("Element" "\\`tag_name\\'" nil nil))
  "Settings for `treesit-simple-imenu'.")

(defvar html-ts-mode--treesit-defun-type-regexp
  "element"
  "Settings for `treesit-defun-type-regexp'.")

(defun html-ts-mode--defun-name (node)
  "Return the defun name of NODE.
Return nil if there is no name or if NODE is not a defun node."
  (when (equal (treesit-node-type node) "tag_name")
    (treesit-node-text node t)))

;;;###autoload
(define-derived-mode html-ts-mode html-mode "HTML"
  "Major mode for editing Html, powered by tree-sitter."
  :group 'html

  (unless (treesit-ready-p 'html)
    (error "Tree-sitter for HTML isn't available"))

  (setq treesit-primary-parser (treesit-parser-create 'html))

  ;; Indent.
  (setq-local treesit-simple-indent-rules html-ts-mode--indent-rules)

  ;; Navigation.
  (setq-local treesit-defun-type-regexp html-ts-mode--treesit-defun-type-regexp)

  (setq-local treesit-defun-name-function #'html-ts-mode--defun-name)

  (setq-local treesit-thing-settings html-ts-mode--treesit-things-settings)

  ;; Font-lock.
  (setq-local treesit-font-lock-settings html-ts-mode--font-lock-settings)
  (setq-local treesit-font-lock-feature-list html-ts-mode--treesit-font-lock-feature-list)

  ;; Imenu.
  (setq-local treesit-simple-imenu-settings html-ts-mode--treesit-simple-imenu-settings)

  ;; Outline minor mode.
  (setq-local treesit-outline-predicate "\\`element\\'")
  ;; `html-ts-mode' inherits from `html-mode' that sets
  ;; regexp-based outline variables.  So need to restore
  ;; the default values of outline variables to be able
  ;; to use `treesit-outline-predicate' above.
  (kill-local-variable 'outline-regexp)
  (kill-local-variable 'outline-heading-end-regexp)
  (kill-local-variable 'outline-level)

  (treesit-major-mode-setup))

(derived-mode-add-parents 'html-ts-mode '(html-mode))

(if (treesit-ready-p 'html)
    (add-to-list 'auto-mode-alist '("\\.html\\'" . html-ts-mode)))

(provide 'html-ts-mode)

;;; html-ts-mode.el ends here

--nextPart2576831.XAFRqVoOGU
Content-Disposition: attachment; filename="mhtml-ts-mode.el"
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-emacs-lisp; charset="UTF-8"; name="mhtml-ts-mode.el"

;;; mhtml-ts-mode.el --- Major mode for HTML using tree-sitter -*- lexical-=
binding: t; -*-

;; Copyright (C) 2024 Free Software Foundation, Inc.

;; Author: Vincenzo Pupillo <v.pupillo@HIDDEN>
;; Maintainer: Vincenzo Pupillo <v.pupillo@HIDDEN>
;; Created: Nov 2024
;; Keywords: HTML languages hypermedia tree-sitter

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:
;;
;; This package provides `mhtml-ts-mode' which is a major mode
;; for editing HTML files with embedded JavaScript and CSS.
;; Tree Sitter is used to parse each of these languages.
;;
;; Please note that this package requires `html-ts-mode', which
;; registers itself as the major mode for editing HTML.
;;
;; This package is compatible and has been tested with the following
;; tree-sitter grammars:
;; * https://github.com/tree-sitter/tree-sitter-html
;; * https://github.com/tree-sitter/tree-sitter-javascript
;; * https://github.com/tree-sitter/tree-sitter-jsdoc
;; * https://github.com/tree-sitter/tree-sitter-css
;;
;; Features
;;
;; * Indent
;; * Flymake
;; * IMenu
;; * Navigation
;; * Which-function
;; * Tree-sitter parser installation helper

;;; Code:

(require 'treesit)
(require 'html-ts-mode)
(require 'css-mode) ;; for embed css into html
(require 'js) ;; for embed javascript into html

(eval-when-compile
  (require 'rx))

;; This tells the byte-compiler where the functions are defined.
;; Is only needed when a file needs to be able to byte-compile
;; in a Emacs not built with tree-sitter library.
(treesit-declare-unavailable-functions)

;; In a multi-language major mode can be useful to have an "installer" to
;; simplify the installation of the grammars supported by the major-mode.
(defvar mhtml-ts-mode--language-source-alist
  '((html . ("https://github.com/tree-sitter/tree-sitter-html"  "v0.23.2"))
    (javascript . ("https://github.com/tree-sitter/tree-sitter-javascript" =
"v0.23.1"))
    (jsdoc . ("https://github.com/tree-sitter/tree-sitter-jsdoc" "v0.23.2"))
    (css . ("https://github.com/tree-sitter/tree-sitter-css" "v0.23.1")))
  "Treesitter language parsers required by `mhtml-ts-mode'.
You can customize this variable if you want to stick to a specific
commit and/or use different parsers.")

(defun mhtml-ts-mode-install-parsers ()
  "Install all the required treesitter parsers.
`mhtml-ts-mode--language-source-alist' defines which parsers to install."
  (interactive)
  (let ((treesit-language-source-alist mhtml-ts-mode--language-source-alist=
))
    (dolist (item mhtml-ts-mode--language-source-alist)
      (treesit-install-language-grammar (car item)))))

;;; Custom variables

(defgroup mhtml-ts-mode nil
  "Major mode for editing HTML files, based on `html-ts-mode'.
Works with JS and CSS and for that use `js-ts-mode' and `css-ts-mode'."
  :prefix "mhtml-ts-mode-"
  ;; :group 'languages
  :group 'html)

(defcustom mhtml-ts-mode-js-css-indent-offset 2
  "JavaScript and CSS indent spaces related to the <script> and <style> HTM=
L tags.
By default should have same value as `html-ts-mode-indent-offset'."
  :tag "HTML javascript or css indent offset"
  :version "31.1"
  :type 'integer
  :safe 'integerp)

(defcustom mhtml-ts-mode-pretty-print-command
  ;; prefer tidy because it's used by sgml-mode
  (let ((executable nil))
    (cond ((setq executable (executable-find "tidy"))
           (format
            "%s --gnu-emacs yes --wrap 0 --indent-spaces %s -q -i -"
            executable html-ts-mode-indent-offset))
          ((setq executable (executable-find "xmllint"))
           (format "%s --html --quiet --format -" executable))
          (t "Install tidy, ore some other HTML pretty print tool, and set =
`mhtml-ts-mode-pretty-print-command'.")))
  "The command to pretty print the current HTML buffer."
  :type 'string
  :version "31.1")

(defvar mhtml-ts-mode--js-css-indent-offset
  mhtml-ts-mode-js-css-indent-offset
  "Internal copy of `mhtml-ts-mode-js-css-indent-offset'.
The value changes, by `mhtml-ts-mode--tag-relative-indent-offset' according=
 to
the value of `mhtml-ts-mode-tag-relative-indent'.")

(defun mhtml-ts-mode--tag-relative-indent-offset (sym val)
  "Custom setter for `mhtml-ts-mode-tag-relative-indent'.
Apart from setting the default value of SYM to VAL, also change the
value of SYM in `mhtml-ts-mode' buffers to VAL.  SYM should be
`mhtml-ts-mode-tag-relative-indent', and VAL should be t, nil or
`ignore'.  When sym is `mhtml-ts-mode-tag-relative-indent' set the
value of `mhtml-ts-mode--js-css-indent-offset' to 0 if VAL is t,
otherwise to `mhtml-ts-mode-js-css-indent-offset'."
  (set-default sym val)
  (when (eq sym 'mhtml-ts-mode-tag-relative-indent)
    (setq
     mhtml-ts-mode--js-css-indent-offset
     (if (eq val t)
         mhtml-ts-mode-js-css-indent-offset
       0))))

(defcustom mhtml-ts-mode-tag-relative-indent t
  "How <script> and <style> bodies are indented relative to the tag.

When t, indentation looks like:

  <script>
    code();
  </script>

When nil, indentation of the tag body starts just below the
tag, like:

  <script>
  code();
  </script>

When `ignore', the tag body starts in the first column, like:

  <script>
code();
  </script>"
  :type '(choice (const nil) (const t) (const ignore))
  :safe 'symbolp
  :set #'mhtml-ts-mode--tag-relative-indent-offset
  :version "31.1")

(defcustom mhtml-ts-mode-css-fontify-colors t
  "Whether CSS colors should be fontified using the color as the background.
If non-nil, text representing a CSS color will be fontified
such that its background is the color itself.
Works like `css--fontify-region'."
  :tag "HTML colors the CSS properties values."
  :version "31.1"
  :type 'boolean
  :safe 'booleanp)

(defvar mhtml-ts-mode-saved-pretty-print-command nil
  "The command last used to pretty print in this buffer.")

(defun mhtml-ts-mode-pretty-print (command)
  "Prettify the current buffer.
Argument COMMAND The command to use."
  (interactive
   (list (read-string
          "Prettify command: "
          (or mhtml-ts-mode-saved-pretty-print-command
              (concat mhtml-ts-mode-pretty-print-command " ")))))
  (setq mhtml-ts-mode-saved-pretty-print-command command)
  (save-excursion
    (shell-command-on-region
     (point-min) (point-max)
     command (buffer-name) t
     "*mhtml-ts-mode-pretty-pretty-print-errors*" t)))

(defun mhtml-ts-mode--switch-fill-defun (&rest arguments)
  "Switch between `fill-paragraph' and `prog-fill-reindent-defun'.
In an HTML region it calls `fill-paragraph' as does `html-ts-mode',
otherwise it calls `prog-fill-reindent-defun'.
Optional ARGUMENTS to to be passed to it."
  (interactive)
  (if (eq (treesit-language-at (point)) 'html)
      (funcall-interactively #'fill-paragraph arguments)
    (funcall-interactively #'prog-fill-reindent-defun arguments)))

(defvar-keymap mhtml-ts-mode-map
  :doc "Keymap for `mhtml-ts-mode' buffers."
  :parent html-mode-map
  ;; `mhtml-ts-mode' derive from `html-ts-mode' so the keymap is the
  ;; same, we need to add some mapping from others languages.
  "C-c C-f" #'css-cycle-color-format
  "M-q" #'mhtml-ts-mode--switch-fill-defun)

;; Put css menu into the menu-bar.
(easy-menu-define mhtml-ts-mode-menu mhtml-ts-mode-map
  "Menu bar for `mhtml-ts-mode'."
  css-mode--menu)

;; Not used at the moment.
(defun mthml-ts-mode--js-language-at-point (point)
  "Return the language at POINT assuming the point is within a Javascript r=
egion."
  (let* ((node (treesit-node-at point 'javascript))
         (node-type (treesit-node-type node))
         (node-start (treesit-node-start node))
         (node-end (treesit-node-end node)))
    (if (not (treesit-ready-p 'jsdoc t))
        'javascript
      (if (equal node-type "comment")
          (save-excursion
            ;; (message "node start =3D %s , end =3D %s" node-start node-en=
d)
            (goto-char node-start)
            (if (search-forward "/**" node-end t)
                'jsdoc
              'javascript))
        'javascript))))

;; To enable some basic treesiter functionality, you should define
;; a function that recognizes which grammar is used at-point.
;; This function should be assigned to `treesit-language-at-point-function'
(defun mhtml-ts-mode--language-at-point (point)
  "Return the language at POINT assuming the point is within a HTML buffer."
  (let* ((node (treesit-node-at point 'html))
         (parent (treesit-node-parent node))
         (node-query (format "(%s (%s))"
                             (treesit-node-type parent)
                             (treesit-node-type node))))
    (cond
     ((equal "(script_element (raw_text))" node-query) 'javascript)
     ((equal "(style_element (raw_text))" node-query) 'css)
     (t 'html))))

;; Custom font-lock function that's used to apply color to css color
;; The signature of the function should be conforming to signature
;; QUERY-SPEC required by `treesit-font-lock-rules'.
(defun mhtml-ts-mode--colorize-css-value (node override start end &rest _)
  "Colorize CSS property value like `css--fontify-region'.
=46or NODE, OVERRIDE, START, and END, see `treesit-font-lock-rules'."
  (if (and mhtml-ts-mode-css-fontify-colors
           (string-equal "plain_value" (treesit-node-type node)))
      (let ((color (css--compute-color start (treesit-node-text node t))))
        (when color
          (with-silent-modifications
            (add-text-properties
             (treesit-node-start node) (treesit-node-end node)
             (list 'face (list :background color
                               :foreground (readable-foreground-color
                                            color)
                               :box '(:line-width -1)))))))
    (treesit-fontify-with-override
     (treesit-node-start node) (treesit-node-end node)
     'font-lock-variable-name-face
     override start end)))

;; Embedded languages =E2=80=8B=E2=80=8Bshould be indented according to the=
 language
;; that embeds them.
;; This function signature complies with `treesit-simple-indent-rules'
;; ANCHOR.
(defun mhtml-ts-mode--js-css-tag-bol (_node _parent &rest _)
  "Find the first non-space characters of html tags <script> or <style>.
Return `line-beginning-position' when `treesit-node-at' is html, or
`mhtml-ts-mode-tag-relative-indent' is equal to ignore.
NODE and PARENT are ignored."
  (if (or (eq (treesit-language-at (point)) 'html)
          (eq mhtml-ts-mode-tag-relative-indent 'ignore))
      (line-beginning-position)
    ;; Ok, we are in js or css block.
    (save-excursion
      (re-search-backward "<script.*>\\|<style.*>" nil t))))

;; Treesit supports 4 level of decoration, `treesit-font-lock-level'
;; define which level to use.  Major modes categorize their fontification
;; features, these categories are defined by `treesit-font-lock-rules' of
;; each major-mode using :feature keyword.
;; In a multiple language Major mode it's a good idea to provide, for each
;; level, the union of the :feature of the same level.
;; TODO: Since the feature-list is not defined per "parser" (like, for
;; example, the thing-settings), the same feature can appear in
;; different levels, so the appearance of a multiple main mode can be
;; different from the main mode used.  For e.g the feature "function" is
;; at level 4 for Javascript while it is at level 3 for CSS.
(defvar mhtml-ts-mode--treesit-font-lock-feature-list
  (treesit-merge-font-lock-feature-list
   html-ts-mode--treesit-font-lock-feature-list
   (treesit-merge-font-lock-feature-list
    js--treesit-font-lock-feature-list
    css--treesit-font-lock-feature-list))
  "Settings for `treesit-font-lock-feature-list'.")

(defvar mhtml-ts-mode--treesit-font-lock-settings
  (append html-ts-mode--font-lock-settings
          js--treesit-font-lock-settings
          ;; Let's replace a css rule with a new one that adds color to
          ;; the css value.
          (treesit-replace-font-lock-feature-settings
           (treesit-font-lock-rules
            :language 'css
            :override t
            :feature 'variable
            '((plain_value) @font-lock-variable-name-face
              (plain_value) @mhtml-ts-mode--colorize-css-value))
           css--treesit-settings))
  "Settings for `treesit-font-lock-settings'.")

(defvar mhtml-ts-mode--treesit-thing-settings
  (append
   html-ts-mode--treesit-things-settings
   js--treesit-thing-settings)
  "Settings for `treesit-thing-settings'.")

(defvar mhtml-ts-mode--treesit-indent-rules
  (treesit--indent-rules-optimize
   (append html-ts-mode--indent-rules
           ;; Extended rules for js and css, to
           ;; indent appropriately when injected
           ;; into html
           (treesit-modify-indent-rules
            `((javascript ((parent-is "program")
                           mhtml-ts-mode--js-css-tag-bol
                           mhtml-ts-mode--js-css-indent-offset)))
            js--treesit-indent-rules
            :replace)
           (treesit-modify-indent-rules
            `((css ((parent-is "stylesheet")
                    mhtml-ts-mode--js-css-tag-bol
                    mhtml-ts-mode--js-css-indent-offset)))
            css--treesit-indent-rules 'prepend)
           :replace))
  "Settings for `treesit-simple-indent-rules'.")

(defvar mhtml-ts-mode--treesit-aggregated-simple-imenu-settings
  `((html ,@html-ts-mode--treesit-simple-imenu-settings)
    (javascript ,@js--treesit-simple-imenu-settings)
    (css ,@css--treesit-simple-imenu-settings))
  "Settings for `treesit-simple-imenu'.")

;; TODO: treesit-defun-type-regexp should have an aggregated version,
;; like treesit-aggregated-simple-imenu-settings. Otherwise we can't
;; reuse the regex defined in the major mode we use.
(defvar mhtml-ts-mode--treesit-defun-type-regexp
  (regexp-opt '("class_declaration"
                "method_definition"
                "function_declaration"
                "lexical_declaration"
                "element"
                "rule_set"))
  "Settings for `treesit-defun-type-regexp'.")

;; In order to support `prettify-symbols-mode', just `append' the prettify
;; alist of all the languages. In our case only javascript defined this ali=
st.
(defvar mhtml-ts-mode--prettify-symbols-alist js--prettify-symbols-alist
  "Alist of symbol prettifications for various supported languages.")

;; In order to support `which-fuction-mode' we should define
;; a function that return the defun name.
;; In a multilingual treesit mode, this can be implemented simply by
;; calling language-specific functions.
(defun mhtml-ts-mode--defun-name (node)
  "Return the defun name of NODE.
Return nil if there is no name or if NODE is not a defun node."
    (let ((html-name (html-ts-mode--defun-name node))
          (js-name (js--treesit-defun-name node))
          (css-name (css--treesit-defun-name node)))
      (cond
       (html-name html-name)
       (js-name js-name)
       (css-name css-name))))

;;; Flymake integration

(defvar-local mhtml-ts-mode--flymake-process nil
  "Store the Flymake process.")

(defun mhtml-ts-mode-flymake-mhtml (report-fn &rest _args)
  "MHTML backend for Flymake.
Calls REPORT-FN directly. Requires tidy."
  (when (process-live-p mhtml-ts-mode--flymake-process)
    (kill-process mhtml-ts-mode--flymake-process))
  (let ((tidy (executable-find "tidy"))
        (source (current-buffer))
        (diagnostics-pattern (eval-when-compile
                               (rx bol
                                   "line " (group (+ num))    ;; :1 line
                                   " column " (group (+ num)) ;; :2 column
                                   " - " (group (+? nonl))    ;; :3 type
                                   ": " (group (+? nonl))     ;; :4 msg
                                   eol))))
    (if (not tidy)
        (error "Unable to find tidy command.")
      (save-restriction
        (widen)
        (setq mhtml-ts-mode--flymake-process
              (make-process
               :name "mhtml-ts-mode-flymake"
               :noquery t
               :connection-type 'pipe
               :buffer (generate-new-buffer "*mhtml-ts-mode-flymake*")
               :command `(,tidy "--gnu-emacs" "yes" "-e" "-q")
               :sentinel
               (lambda (proc _event)
                 (when (eq 'exit (process-status proc))
                   (unwind-protect
                       (if (with-current-buffer source
                             (eq proc mhtml-ts-mode--flymake-process))
                           (with-current-buffer (process-buffer proc)
                             (goto-char (point-min))
                             (let (diags)
                               (while (search-forward-regexp diagnostics-pa=
ttern nil t)
                                 (let* ((pos
                                         (flymake-diag-region
                                          source
                                          (string-to-number (match-string 1=
))
                                          (string-to-number (match-string 2=
)))) ;; line and column
                                        (type (cond ((equal (match-string 3=
) "Warning") :warning)
                                                    ((equal (match-string 3=
) "Error") :error))) ;; type of message
                                        (msg (match-string 4))) ;; message
                                   (push (flymake-make-diagnostic source (c=
ar pos) (cdr pos) type msg)
                                         diags)))
                               (funcall report-fn diags)))
                         (flymake-log :warning "Canceling obsolete check %s=
" proc))
                     (kill-buffer (process-buffer proc)))))))
        (process-send-region mhtml-ts-mode--flymake-process (point-min) (po=
int-max))
        (process-send-eof mhtml-ts-mode--flymake-process)))))

(define-derived-mode mhtml-ts-mode html-ts-mode
  '("HTML+" (:eval (let ((lang (mhtml-ts-mode--language-at-point (point))))
                     (cond ((eq lang 'html) "")
                           ((eq lang 'javascript) "JS")
                           ((eq lang 'css) "CSS")))))
  "Major mode for editing HTML with embedded JavaScript and CSS.
Powered by tree-sitter."
  (if (not (and
            (treesit-ready-p 'html)
            (treesit-ready-p 'javascript)
            (treesit-ready-p 'css)))
      (error "Tree-sitter parsers for HTML isn't available.  You can
    install the parsers with M-x `mhtml-ts-mode-install-parsers'")

    ;; When an language is embedded, you should initialize some variable
    ;; just like it's done in the original mode.

    ;; Comment.
    ;; indenting settings for js-ts-mode.
    (c-ts-common-comment-setup)
    (setq-local comment-multi-line t)

    ;; Font-lock.

    ;; There are two ways to handle embedded code:
    ;; 1. Use a single parser for all the embedded code in the buffer. In
    ;; this case, the embedded code blocks are concatenated together and are
    ;; seen as a single continuous document to the parser.
    ;; 2. Each embedded code block gets its own parser. Each parser only se=
es
    ;; that particular code block.

    ;; If you go with 2 for a language, the local parsers are created and
    ;; destroyed automatically by Emacs. So don't create a global parser for
    ;; that embedded language here.

    ;; Create the parsers, only the global ones.
    ;; jsdoc is a local parser, don't create a parser for it.
    (treesit-parser-create 'css)
    (treesit-parser-create 'javascript)

    ;; Multi-language modes must set the  primary parser.
    (setq-local treesit-primary-parser (treesit-parser-create 'html))

    (setq-local treesit-range-settings
                (treesit-range-rules
                 :embed 'javascript
                 :host 'html
                 '((script_element
                    (start_tag (tag_name))
                    (raw_text) @cap))

                 ;; Another rule could be added that when it matches an
                 ;; attribute_value that has as its parent an
                 ;; attribute_name "style" it captures it and then
                 ;; passes it to the css parser.
                 :embed 'css
                 :host 'html
                 '((style_element
                    (start_tag (tag_name))
                    (raw_text) @cap))))

    ;; jsdoc is not mandatory for js-ts-mode, so we respect this by
    ;; adding jsdoc range rules only when jsdoc is available.
    (when (treesit-ready-p 'jsdoc t)
      (setq-local treesit-range-settings
                  (append treesit-range-settings
                          (treesit-range-rules
                           :embed 'jsdoc
                           :host 'javascript
                           :local t
                           `(((comment) @cap
                              (:match ,js--treesit-jsdoc-beginning-regexp @=
cap))))))
      (setq-local c-ts-common--comment-regexp
                  js--treesit-jsdoc-comment-regexp))


    ;; Many treesit fuctions need to know the language at-point.
    ;; So you should define such a function.
    (setq-local treesit-language-at-point-function #'mhtml-ts-mode--languag=
e-at-point)
    (setq-local prettify-symbols-alist mhtml-ts-mode--prettify-symbols-alis=
t)
   =20
    ;; Indent.

    ;; Since mhtl-ts-mode inherits indentation rules from html-ts-mode, js
    ;; and css, if you want to change the offset you have to act on the
    ;; *-offset variables defined for those languages.

    ;; JavaScript and CSS must be indented relative to their code block.
    ;; This is done by inserting a special rule before the normal
    ;; indentation rules of these languages.
    ;; The value of mhtml-ts-mode--js-css-indent-offset changes based on
    ;; mhtml-ts-mode-tag-relative-indent and can be used to indent
    ;; JavaScript and CSS code relative to the HTML that contains them,
    ;; just like in mhtml-mode.
    (setq-local treesit-simple-indent-rules mhtml-ts-mode--treesit-indent-r=
ules)

    ;; Navigation.

    ;; This is for which-function-mode
    (setq-local treesit-defun-type-regexp mhtml-ts-mode--treesit-defun-type=
=2Dregexp)

    ;; This is for finding defun name, it's used by IMenu as default
    ;; function no specific functions are defined.
    (setq-local treesit-defun-name-function #'mhtml-ts-mode--defun-name)

    ;; Define what are 'thing' for treesit.
    ;; 'Thing' is a symbol representing the thing, like `defun', `sexp', or
    ;; `sentence'.
    ;; As an alternative, if you want just defun, you can define a `treesit=
=2Ddefun-type-regexp'.
    (setq-local treesit-thing-settings mhtml-ts-mode--treesit-thing-setting=
s)

    ;; Font-lock.

    ;; In a multi-language scenario, font lock settings are usually a
    ;; concatenation of language rules. As you can see, it is possible
    ;; to extend/modify the default rule or use a different set of
    ;; rules. See `php-ts-mode--custom-html-font-lock-settings' for more
    ;; advanced usage.
    (setq-local treesit-font-lock-settings mhtml-ts-mode--treesit-font-lock=
=2Dsettings)

    ;; Tells treesit the list of features to fontify.
    (setq-local treesit-font-lock-feature-list mhtml-ts-mode--treesit-font-=
lock-feature-list)

    ;; Imenu

    ;; Setup Imenu: if no function is specified, try to find an object
    ;; using `treesit-defun-name-function'.
    (setq-local treesit-aggregated-simple-imenu-settings
                mhtml-ts-mode--treesit-aggregated-simple-imenu-settings)

    (treesit-major-mode-setup)
   =20
    ;; This is sort of a prog-mode as well as a text mode.
    (run-mode-hooks 'prog-mode-hook)

    ;; Flymake
    (add-hook 'flymake-diagnostic-functions #'mhtml-ts-mode-flymake-mhtml n=
il 'local)))

;; Add nome extra parents.
(derived-mode-add-parents 'mhtml-ts-mode '(css-mode js-mode))

(when (and (treesit-ready-p 'html) (treesit-ready-p 'javascript) (treesit-r=
eady-p 'css))
  (add-to-list
   'auto-mode-alist '("\\.[sx]?html?\\(\\.[a-zA-Z_]+\\)?\\'" . mhtml-ts-mod=
e)))

(provide 'mhtml-ts-mode)
;;; mhtml-ts-mode.el ends here

--nextPart2576831.XAFRqVoOGU--







Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 15 Jan 2025 07:51:53 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Jan 15 02:51:53 2025
Received: from localhost ([127.0.0.1]:56832 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tXyBs-0006YH-P7
	for submit <at> debbugs.gnu.org; Wed, 15 Jan 2025 02:51:53 -0500
Received: from relay4-d.mail.gandi.net ([2001:4b98:dc4:8::224]:33513)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.84_2) (envelope-from <juri@HIDDEN>) id 1tXyBn-0006Y0-2F
 for 74610 <at> debbugs.gnu.org; Wed, 15 Jan 2025 02:51:50 -0500
Received: by mail.gandi.net (Postfix) with ESMTPSA id AD082E0004;
 Wed, 15 Jan 2025 07:51:39 +0000 (UTC)
From: Juri Linkov <juri@HIDDEN>
To: Vincenzo Pupillo <v.pupillo@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
In-Reply-To: <2462456.cojqenx9y0@fedora> (Vincenzo Pupillo's message of "Tue, 
 14 Jan 2025 22:41:01 +0100")
Organization: LINKOV.NET
References: <3532547.LZWGnKmheA@fedora>
 <018B485B-8F4D-4472-BD21-384C7EC9E31C@HIDDEN>
 <D06621B4-736C-4729-A8E4-3D5885F498F4@HIDDEN>
 <2462456.cojqenx9y0@fedora>
Date: Wed, 15 Jan 2025 09:50:06 +0200
Message-ID: <87cygofrdd.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/31.0.50 (x86_64-pc-linux-gnu)
MIME-Version: 1.0
Content-Type: text/plain
X-GND-Sasl: juri@HIDDEN
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 74610
Cc: Yuan Fu <casouri@HIDDEN>, Eli Zaretskii <eliz@HIDDEN>,
 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

> this is an updated version of mhtml-ts-mode. 

Thanks.  Please also attach a new version of the mhtml-ts-mode.el file.

> I have tried to reduce as much as possible copies of parts of the major modes 
> from which it is derived.
>
> To do this, I had to move some values that were assigned directly to treesit's 
> own variables  (in ccs-mode.el, in js.el, and in html-ts-mode.el) into new 
> variables. 

I had a thought that maybe for treesit-thing-settings we could have
a global registry like auto-mode-alist, not a buffer-local value.
Then modes could just add own info to it. e.g.

(add-to-list 'treesit-thing-global-settings
             '(javascript (sentence ...)))

This can be shared between embedded ts-modes.




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 14 Jan 2025 21:41:15 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Jan 14 16:41:14 2025
Received: from localhost ([127.0.0.1]:55997 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tXoev-0000Fx-Ni
	for submit <at> debbugs.gnu.org; Tue, 14 Jan 2025 16:41:14 -0500
Received: from mail-ej1-x631.google.com ([2a00:1450:4864:20::631]:54621)
 by debbugs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.84_2) (envelope-from <v.pupillo@HIDDEN>)
 id 1tXoes-0000Fa-NY
 for 74610 <at> debbugs.gnu.org; Tue, 14 Jan 2025 16:41:12 -0500
Received: by mail-ej1-x631.google.com with SMTP id
 a640c23a62f3a-aa6a92f863cso1125209566b.1
 for <74610 <at> debbugs.gnu.org>; Tue, 14 Jan 2025 13:41:10 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1736890864; x=1737495664; darn=debbugs.gnu.org;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:from:to:cc:subject:date
 :message-id:reply-to;
 bh=4CI6hVZJXS1vrdOFX2X1pM5Q2LU8auTpS6gVcHb414g=;
 b=jREh4YbGKyJvouxfOmV/NqJMd1wOlmElZXnhmuD8hu0Sidg0H6FeJa/hrP3Z37bScl
 VEbdm6GepPNx+wRsAZa62BCMjPOmqq8mWjuOEXHy6ABQPBzA6cX19M/W4g87msIX6W8m
 oedhZHiQF68cyRczFca52+gtu4fLmzcXNvlKbggDQVwKirm2udWjx5rcRgpgRMgPMQ4e
 BjdqskZhbfdBkt+QHgv0GcdChaLY9YLfDxmMaQH47Qe8G3waezFutWuNyCl/uW1Is4OB
 xNOkT6Pl3Y7fybgefbVdK14b+Ht4tn2DnPGoh2Pu6DkV6DX2B3Vp4KLttCj9WF8IVuwC
 hvjw==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1736890864; x=1737495664;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=4CI6hVZJXS1vrdOFX2X1pM5Q2LU8auTpS6gVcHb414g=;
 b=FMpYEf4qU8Huk4po3zzJ3T2DQSZvOoe9CtAIJeC7oo6ANamJr2OB9zwHV2AxzoHLz/
 vhn+zISd9+6xpwJRPJWxnSm9uKsCAEc7ZoY89Ne+KsTE0f6bIp+70CslGsCytd++/t+A
 9VzXstSw9mF6in4xpoSdtlqx6/uP6phT5WOdzjOskMibIaZFcRaoF9rinZwhGpiiWoRK
 oPCbuT+8VnIzK1yGue6dLNvjU4BLr80oWiBntWmBbkrCo+vJsg7eemL+Eb/GR6JJ+1vY
 otTTAbv/fo2z7e9ISj0iB0GolZthLR92HT6nc4J5hZntDsjQtgm5AI36JX67aXBNNBB2
 MiVQ==
X-Gm-Message-State: AOJu0Ywy5orrRn2GtcxrVXO6BlOLi5iNTe8nOpCwJQERPKmrxADGgUSp
 cL1qjY/I0EoAmTK/Ql887EAQU4krKDOw7y5q+2iwoOISKWQJamgd
X-Gm-Gg: ASbGncsIU3QoN3pzMxz9m/Bv0XV918gnGvqGkxHujzHnKwc+JQsbYkQDeZZP7fiKuZc
 WPpAKoyK37lkcgsU8v2/f3sFJUYpKgIJ02MTYlG0DtSiY+6NMaK8wNAwmj5AO34Qv8AQXyk2E7M
 jtJxsKyl3obJXPSbCTA0pxtzZmwoHfULZtEscV/NAwy/ubnIbZhkgPUpFovti3v/6wnbhtyHU8r
 8I7t25c63MbNQhv/igdOtNIQUAkFAP7QzXC2y6NTXIXBhgCxdRKxr95srXJLmaWCPCxyLsGb8ua
 Id152m6nkmA93OC8eNWL
X-Google-Smtp-Source: AGHT+IGmgt3O8NeSVzQmcGl5WttyLFFo+VSqv0lsUh28ZADsxsvAGkCqB1vvf/Bdnbg5yysoVJtyIw==
X-Received: by 2002:a17:907:1c21:b0:aab:d8df:9bbb with SMTP id
 a640c23a62f3a-ab2abc925cbmr2744160266b.41.1736890863802; 
 Tue, 14 Jan 2025 13:41:03 -0800 (PST)
Received: from fedora.localnet (2-230-139-124.ip202.fastwebnet.it.
 [2.230.139.124]) by smtp.gmail.com with ESMTPSA id
 a640c23a62f3a-ab2d0ecdf99sm649732366b.17.2025.01.14.13.41.02
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Tue, 14 Jan 2025 13:41:03 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: Yuan Fu <casouri@HIDDEN>, Juri Linkov <juri@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Tue, 14 Jan 2025 22:41:01 +0100
Message-ID: <2462456.cojqenx9y0@fedora>
In-Reply-To: <D06621B4-736C-4729-A8E4-3D5885F498F4@HIDDEN>
References: <3532547.LZWGnKmheA@fedora>
 <018B485B-8F4D-4472-BD21-384C7EC9E31C@HIDDEN>
 <D06621B4-736C-4729-A8E4-3D5885F498F4@HIDDEN>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="nextPart4608876.niJfEyVGOH"
Content-Transfer-Encoding: 7Bit
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This is a multi-part message in MIME format.

--nextPart4608876.niJfEyVGOH
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"

Ciao Yuan and Juri,=20
this is an updated version of mhtml-ts-mode.=20
I have tried to reduce as much as possible copies of parts of the major mod=
es=20
from which it is derived.
To do this, I had to move some values that were assigned directly to treesi=
t's=20
own variables  (in ccs-mode.el, in js.el, and in html-ts-mode.el) into new=
=20
variables.=20
I also added three new functions to treesit.el to make it easier to combine=
=20
parts derived from the other major modes. So now any changes to these new=20
variables are directly reflected in the behavior of mhtml-ts-mode.
There are a few things I would like to highlight:
1. treesit-font-lock-feature-lists are not defined per parser, so simply=20
merging the different lists will cause display differences from the origina=
l=20
major-modes; for example =E2=80=9Cfunction=E2=80=9D is defined at level 3 i=
n css-ts-mode but=20
at level 4 in js-ts-mode.
2. treesit-defun-type-regexp has the same problem as treesit-font-lock-
feature-list, so I had to define it myself.

But other than that, it works pretty well.

IMHO, a global "list" where you can define "font-lock", "indent-list", "fon=
t-
lock-feature", etc. by language (perhaps with getter and setter methods) mi=
ght=20
make it easier to define new multilingual major-modes. It could improve the=
=20
decoupling between multilingual major-modes and the major-modes they are=20
derived from. It could also better decouple the internal implementation of=
=20
treesit.el from the treesitter-based major-modes.

Let me know what you think.

Thanks.

Vincenzo

p.s. In this version I also added support for wich-function-mode, for flyma=
ke=20
and a pretty-print function.

In data marted=C3=AC 24 dicembre 2024 23:18:39 Ora standard dell=E2=80=99Eu=
ropa centrale,=20
Yuan Fu ha scritto:
> > On Dec 24, 2024, at 12:37=E2=80=AFAM, Yuan Fu <casouri@HIDDEN> wrote:
> >> On Dec 14, 2024, at 2:37=E2=80=AFAM, Vincenzo Pupillo <v.pupillo@gmail=
=2Ecom>
> >> wrote:
> >>=20
> >> In data mercoled=C3=AC 11 dicembre 2024 05:54:09 Ora standard dell=E2=
=80=99Europa
> >> centrale,>>=20
> >> Yuan Fu ha scritto:
> >>>> On Dec 3, 2024, at 6:29=E2=80=AFAM, Vincenzo Pupillo <v.pupillo@gmai=
l.com>
> >>>> wrote:
> >>>>=20
> >>>> In data domenica 1 dicembre 2024 07:01:21 Ora standard dell=E2=80=99=
Europa
> >>>> centrale,>
> >>>>=20
> >>>> Yuan Fu ha scritto:
> >>>>> It's not uncommon to see different indent offset for CSS and
> >>>>> Javascript, so it's a good idea to have separate control for them.
> >>>>=20
> >>>> Is the behavior the same as mhtml-mode, or would you like something
> >>>> like
> >>>> this?>
> >>>>=20
> >>>>  <style>
> >>>> =20
> >>>>                                z {
> >>>>                               =20
> >>>>                                    color: red;
> >>>>                               =20
> >>>>                                }
> >>>> =20
> >>>>  </style>
> >>>>  <script>
> >>>> =20
> >>>>      function myFunction(p1, p2) {
> >>>>     =20
> >>>>          return p1 * p2;
> >>>>     =20
> >>>>      }
> >>>> =20
> >>>>  </script>
> >>>>=20
> >>>> The mhtml-ts-mode-js-css-indent-offset variable controls only the
> >>>> indentation relative to the <style> and <script> tags.
> >>>=20
> >>> Ah, I see, it=E2=80=99s the offset from the enclosing tag. In that ca=
se it
> >>> should be fine to use a common variable.
> >>>=20
> >>> Yuan
> >>=20
> >> Thank you Yuan.
> >> Attached is the revised patch following your previous comments.
> >> As I already wrote to Dmitry, I am doing some tests to see if
> >> html-ts-mode can be extended and if there is a way to integrate one
> >> multi-language mode into another multi-language mode.
> >>=20
> >> Vincenzo
> >> <0001-Add-mhtml-ts-mode.patch>
> >=20
> > Btw, mhtml-ts-mode--defun-name seems to contain some debugging code? And
> > also I think you should use treesit-node-language.
> >=20
> > Yuan
>=20
> I just added treesit-aggregated-simple-imenu-settings to master. This
> variable will allow you to setup Imenu for multiple languages. Please give
> it a try. I tested locally with mhtml-ts-mode and works well.
>=20
> Yuan


--nextPart4608876.niJfEyVGOH
Content-Disposition: attachment; filename="0001-Add-mhtml-ts-mode.patch"
Content-Transfer-Encoding: 7Bit
Content-Type: text/x-patch; charset="UTF-8";
 name="0001-Add-mhtml-ts-mode.patch"

From 58480feff12b9cf61a91e8be665d5a453ffc415d Mon Sep 17 00:00:00 2001
From: Vincenzo Pupillo <vincenzo.pupillo@HIDDEN>
Date: Tue, 14 Jan 2025 15:40:32 +0100
Subject: [PATCH] Add mhtml-ts-mode.

New major-mode alternative to mhtml-mode, based on treesitter, for
editing files containing html, javascript and css.

* etc/NEWS: Mention the new mode and new functions.
* lisp/textmodes/mhtml-ts-mode.el: New file.
* lisp/progmodes/js.el (js--treesit-thing-settings): New variable.
(js--treesit-font-lock-feature-list); New variable.
(js--treesit-simple-imenu-settings): New variable.
(js--treesit-defun-type-regexp): New variable.
(js--treesit-jsdoc-comment-regexp): New variable.
(js-ts-mode): Use of new variables instead of direct assignment of
values.
* lisp/textmodes/css-mode.el (css-mode--menu): New variable.
(css-mode-map): Use new variable.
(css--treesit-font-lock-feature-list): New variable.
(css--treesit-simple-imenu-settings): New variable.
(css--treesit-defun-type-regexp): New variable.
(cs-ts-mode): Use of new variables instead of direct assignment of
values.
* lisp/textmodes/html-ts-mode.el
(html-ts-mode--treesit-things-settings): New variable.
(html-ts-mode--treesit-font-lock-feature-list): New variable.
(html-ts-mode--treesit-simple-imenu-settings): New variable.
(html-ts-mode--treesit-defun-type-regexp): New variable.
(html-ts-mode): Use of new variables instead of direct assignment of
values.
* lisp/treesit.el
(treesit-merge-font-lock-feature-list): New fuction.
(treesit-replace-font-lock-feature-settings): New fuction.
(treesit-modify-indent-rules): New function.
---
 etc/NEWS                       | 29 ++++++++++++++
 lisp/progmodes/js.el           | 72 +++++++++++++++++++++-------------
 lisp/textmodes/css-mode.el     | 47 ++++++++++++++--------
 lisp/textmodes/html-ts-mode.el | 46 ++++++++++++++--------
 lisp/treesit.el                | 65 ++++++++++++++++++++++++++++++
 5 files changed, 200 insertions(+), 59 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 2f04204ad94..8ce8ccae581 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -948,6 +948,13 @@ destination window is chosen using 'display-buffer-alist'.  Example:
 
 * New Modes and Packages in Emacs 31.1
 
+** New major modes based on the tree-sitter library
+
+*** New major mode 'mhtml-ts-mode'.
+An optional major mode based on the tree-sitter library for editing html
+files. This mode handles indentation, fontification, and commenting for
+embedded JavaScript and CSS.
+
 
 * Incompatible Lisp Changes in Emacs 31.1
 
@@ -1080,6 +1087,28 @@ language symbol.  For example, 'cpp' is translated to "C++".  A new
 variable 'treesit-language-display-name-alist' holds the translations of
 language symbols where that translation is not trivial.
 
++++
+*** New function 'treesit-merge-font-lock-feature-list'.
+This function the merge two tree-sitter font lock feature lists.
+Returns a new font lock feature list with no duplicates in the same level.
+It can be used to merge font lock feature lists in a multi-language major mode.
+
++++
+*** New function 'treesit-replace-font-lock-feature-settings'.
+Given two treesit-font-lock-settings replaces the feature in the second
+font-lock-settings with the same feature in the first
+font-lock-settings. In a multi-linguage major mode it is sometimes
+necessary to replace features from one of the major modes, with others
+that are better suited to the new multilingual context.
+
++++
+*** New function 'treesit-modify-indent-rules'.
+Given two treesit ident rules, it replaces, adds, or prepends the new
+rules to the old ones, then returns a new treesit indent rules.
+In a multi-linguage major mode it is sometimes necessary to modify rules
+from one of the major modes, with others that are better suited to the
+new multilingual context.
+
 +++
 *** New command 'treesit-explore'
 This command replaces 'treesit-explore-mode'.  It turns on
diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
index 101b882c718..c7b301c100c 100644
--- a/lisp/progmodes/js.el
+++ b/lisp/progmodes/js.el
@@ -3901,6 +3901,44 @@ js--treesit-list-nodes
 (defvar js--treesit-jsdoc-beginning-regexp (rx bos "/**")
   "Regular expression matching the beginning of a jsdoc block comment.")
 
+(defvar js--treesit-thing-settings
+  `((javascript
+     (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
+     (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
+     (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
+     (text ,(js--regexp-opt-symbol '("comment"
+                                     "string_fragment")))))
+  "Settings for `treesit-thing-settings'.")
+
+(defvar js--treesit-font-lock-feature-list
+  '(( comment document definition)
+                  ( keyword string)
+                  ( assignment constant escape-sequence jsx number
+                    pattern string-interpolation)
+                  ( bracket delimiter function operator property))
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar js--treesit-simple-imenu-settings
+  `(("Function" "\\`function_declaration\\'" nil nil)
+    ("Variable" "\\`lexical_declaration\\'"
+     js--treesit-valid-imenu-entry nil)
+    ("Class" ,(rx bos (or "class_declaration"
+                          "method_definition")
+                  eos)
+     nil nil))
+  "Settings for `treesit-simple-imenu'.")
+
+(defvar js--treesit-defun-type-regexp
+  (rx (or "class_declaration"
+          "method_definition"
+          "function_declaration"
+          "lexical_declaration"))
+  "Settings for `treesit-defun-type-regexp'.")
+
+(defvar js--treesit-jsdoc-comment-regexp
+  (rx (or "comment" "line_comment" "block_comment" "description"))
+  "Regexp for `c-ts-common--comment-regexp'.")
+
 ;;;###autoload
 (define-derived-mode js-ts-mode js-base-mode "JavaScript"
   "Major mode for editing JavaScript.
@@ -3931,29 +3969,15 @@ js-ts-mode
     ;; Indent.
     (setq-local treesit-simple-indent-rules js--treesit-indent-rules)
     ;; Navigation.
-    (setq-local treesit-defun-type-regexp
-                (rx (or "class_declaration"
-                        "method_definition"
-                        "function_declaration"
-                        "lexical_declaration")))
+    (setq-local treesit-defun-type-regexp js--treesit-defun-type-regexp)
+
     (setq-local treesit-defun-name-function #'js--treesit-defun-name)
 
-    (setq-local treesit-thing-settings
-                `((javascript
-                   (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
-                   (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
-                   (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
-                   (text ,(js--regexp-opt-symbol '("comment"
-                                                   "string_fragment"))))))
+    (setq-local treesit-thing-settings js--treesit-thing-settings)
 
     ;; Fontification.
     (setq-local treesit-font-lock-settings js--treesit-font-lock-settings)
-    (setq-local treesit-font-lock-feature-list
-                '(( comment document definition)
-                  ( keyword string)
-                  ( assignment constant escape-sequence jsx number
-                    pattern string-interpolation)
-                  ( bracket delimiter function operator property)))
+    (setq-local treesit-font-lock-feature-list js--treesit-font-lock-feature-list)
 
     (when (treesit-ready-p 'jsdoc t)
       (setq-local treesit-range-settings
@@ -3963,17 +3987,11 @@ js-ts-mode
                    :local t
                    `(((comment) @capture (:match ,js--treesit-jsdoc-beginning-regexp @capture)))))
 
-      (setq c-ts-common--comment-regexp (rx (or "comment" "line_comment" "block_comment" "description"))))
+      (setq c-ts-common--comment-regexp js--treesit-jsdoc-comment-regexp))
 
     ;; Imenu
-    (setq-local treesit-simple-imenu-settings
-                `(("Function" "\\`function_declaration\\'" nil nil)
-                  ("Variable" "\\`lexical_declaration\\'"
-                   js--treesit-valid-imenu-entry nil)
-                  ("Class" ,(rx bos (or "class_declaration"
-                                        "method_definition")
-                                eos)
-                   nil nil)))
+    (setq-local treesit-simple-imenu-settings js--treesit-simple-imenu-settings)
+
     (treesit-major-mode-setup)
 
     (add-to-list 'auto-mode-alist
diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index 53340195386..35c61e4f66d 100644
--- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -893,13 +893,7 @@ css-mode-syntax-table
     (modify-syntax-entry ?? "." st)
     st))
 
-(defvar-keymap css-mode-map
-  :doc "Keymap used in `css-mode'."
-  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
-  ;; `info-complete-symbol' is not used.
-  "<remap> <complete-symbol>" #'completion-at-point
-  "C-c C-f" #'css-cycle-color-format
-  :menu
+(defvar css-mode--menu
   '("CSS"
     :help "CSS-specific features"
     ["Reformat block" fill-paragraph
@@ -910,7 +904,17 @@ css-mode-map
     ["Describe symbol" css-lookup-symbol
      :help "Display documentation for a CSS symbol"]
     ["Complete symbol" completion-at-point
-     :help "Complete symbol before point"]))
+     :help "Complete symbol before point"])
+    "Menu bar for `css-mode'")
+
+(defvar-keymap css-mode-map
+  :doc "Keymap used in `css-mode'."
+  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
+  ;; `info-complete-symbol' is not used.
+  "<remap> <complete-symbol>" #'completion-at-point
+  "C-c C-f" #'css-cycle-color-format
+  :menu
+  css-mode--menu)
 
 (eval-and-compile
   (defconst css--uri-re
@@ -1771,6 +1775,21 @@ css--extract-index-name
               (replace-regexp-in-string "[\n ]+" " " s)))
            res)))))))
 
+(defvar css--treesit-font-lock-feature-list
+  '((selector comment query keyword)
+    (property constant string)
+    (error variable function operator bracket))
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar css--treesit-simple-imenu-settings
+  `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
+      nil nil))
+  "Settings for `treesit-simple-imenu'.")
+
+(defvar css--treesit-defun-type-regexp
+  "rule_set"
+  "Settings for `treesit-defun-type-regexp'.")
+
 (define-derived-mode css-base-mode prog-mode "CSS"
   "Generic mode to edit Cascading Style Sheets (CSS).
 
@@ -1825,16 +1844,12 @@ css-ts-mode
     ;; Tree-sitter specific setup.
     (setq treesit-primary-parser (treesit-parser-create 'css))
     (setq-local treesit-simple-indent-rules css--treesit-indent-rules)
-    (setq-local treesit-defun-type-regexp "rule_set")
+    (setq-local treesit-defun-type-regexp css--treesit-defun-type-regexp)
     (setq-local treesit-defun-name-function #'css--treesit-defun-name)
     (setq-local treesit-font-lock-settings css--treesit-settings)
-    (setq-local treesit-font-lock-feature-list
-                '((selector comment query keyword)
-                  (property constant string)
-                  (error variable function operator bracket)))
-    (setq-local treesit-simple-imenu-settings
-                `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
-                    nil nil)))
+    (setq-local treesit-font-lock-feature-list css--treesit-font-lock-feature-list)
+    (setq-local treesit-simple-imenu-settings css--treesit-simple-imenu-settings)
+
     (treesit-major-mode-setup)
 
     (add-to-list 'auto-mode-alist '("\\.css\\'" . css-ts-mode))))
diff --git a/lisp/textmodes/html-ts-mode.el b/lisp/textmodes/html-ts-mode.el
index dad49b7ed4c..de945d89ffb 100644
--- a/lisp/textmodes/html-ts-mode.el
+++ b/lisp/textmodes/html-ts-mode.el
@@ -87,6 +87,32 @@ html-ts-mode--font-lock-settings
    `((attribute_name) @font-lock-variable-name-face))
   "Tree-sitter font-lock settings for `html-ts-mode'.")
 
+(defvar html-ts-mode--treesit-things-settings
+  `((html
+     (sexp ,(regexp-opt '("element"
+                          "text"
+                          "attribute"
+                          "value")))
+     (sexp-list ,(regexp-opt '("element")) 'symbols)
+     (sentence "tag")
+     ;; (sentence ,(regex-opt '("start_tag" "end_tag")))
+     (text ,(regexp-opt '("comment" "text")))))
+  "Settings for `treesit-thing-settings'.")
+
+(defvar html-ts-mode--treesit-font-lock-feature-list
+  '((comment keyword definition)
+    (property string)
+    () ())
+  "Settings for `treesit-font-lock-feature-list'.")
+
+(defvar html-ts-mode--treesit-simple-imenu-settings
+  '(("Element" "\\`tag_name\\'" nil nil))
+  "Settings for `treesit-simple-imenu'.")
+
+(defvar html-ts-mode--treesit-defun-type-regexp
+  "element"
+  "Settings for `treesit-defun-type-regexp'.")
+
 (defun html-ts-mode--defun-name (node)
   "Return the defun name of NODE.
 Return nil if there is no name or if NODE is not a defun node."
@@ -107,30 +133,18 @@ html-ts-mode
   (setq-local treesit-simple-indent-rules html-ts-mode--indent-rules)
 
   ;; Navigation.
-  (setq-local treesit-defun-type-regexp "element")
+  (setq-local treesit-defun-type-regexp html-ts-mode--treesit-defun-type-regexp)
 
   (setq-local treesit-defun-name-function #'html-ts-mode--defun-name)
 
-  (setq-local treesit-thing-settings
-              `((html
-                 (sexp ,(regexp-opt '("element"
-                                      "text"
-                                      "attribute"
-                                      "value")))
-                 (list ,(regexp-opt '("element")) 'symbols)
-                 (sentence "tag")
-                 (text ,(regexp-opt '("comment" "text"))))))
+  (setq-local treesit-thing-settings html-ts-mode--treesit-things-settings)
 
   ;; Font-lock.
   (setq-local treesit-font-lock-settings html-ts-mode--font-lock-settings)
-  (setq-local treesit-font-lock-feature-list
-              '((comment keyword definition)
-                (property string)
-                () ()))
+  (setq-local treesit-font-lock-feature-list html-ts-mode--treesit-font-lock-feature-list)
 
   ;; Imenu.
-  (setq-local treesit-simple-imenu-settings
-              '(("Element" "\\`tag_name\\'" nil nil)))
+  (setq-local treesit-simple-imenu-settings html-ts-mode--treesit-simple-imenu-settings)
 
   ;; Outline minor mode.
   (setq-local treesit-outline-predicate "\\`element\\'")
diff --git a/lisp/treesit.el b/lisp/treesit.el
index ac34edaf84d..597090c8543 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -1259,6 +1259,40 @@ treesit-font-lock-recompute-features
                        ((memq feature remove-list) nil)
                        (t current-value))))))
 
+(defun treesit-merge-font-lock-feature-list (features-list-1 features-list-2)
+  "Merge two tree-sitter font lock feature lists.
+Returns a new font lock feature list with no duplicates in the same level.
+It can be used to merge font lock feature lists in a multi-language major mode.
+FEATURES-LIST-1 and FEATURES-LIST-2 are list of lists of feature symbols."
+    (let ((result nil)
+	(features-1 (car features-list-1))
+	(features-2 (car features-list-2)))
+    (while (or features-1 features-2)
+      (cond
+       ((and features-1 (not features-2)) (push features-1 result))
+       ((and (not features-1) features-2) (push features-2 result))
+       ((and features-1 features-2) (push (cl-union features-1 features-2) result)))
+      (setq features-list-1 (cdr features-list-1)
+	    features-list-2 (cdr features-list-2)
+	    features-1 (car features-list-1)
+            features-2 (car features-list-2)))
+    (nreverse result)))
+
+(defun treesit-replace-font-lock-feature-settings (new-settings settings)
+  "Replaces :feature in SETTINGS with :feature from NEW-SETTINGS.
+Both SETTINGS and NEW-SETTINGS must be a value suitable for
+`treesit-font-lock-settings'.
+Return a value suitable for `treesit-font-lock-settings'"
+  (let ((result nil))
+    (dolist (new-setting new-settings)
+      (let ((new-feature (treesit-font-lock-setting-feature new-setting)))
+	(dolist (setting settings)
+	  (let ((feature (treesit-font-lock-setting-feature setting)))
+	    (if (eq new-feature feature)
+		(push new-setting result)
+	      (push setting result))))))
+    (nreverse result)))
+
 (defun treesit-add-font-lock-rules (rules &optional how feature)
   "Add font-lock RULES to the current buffer.
 
@@ -2357,6 +2391,37 @@ treesit--indent-rules-optimize
                             offset)))))
              (cons lang (mapcar #'optimize-rule indent-rules)))))
 
+(defun treesit-modify-indent-rules (new-rules rules &optional how)
+  "Modify RULES using NEW-RULES.
+As default replace rules with the same anchor.
+When HOW is :prepend NEW-RULES are prepend to RULES, when
+:append NEW-RULES are appended to RULES, when :replace (the default)
+NEW-RULES replace rule in RULES which the same anchor."
+  (let ((n-lang (car (car new-rules)))
+	(lang (car (car rules))))
+    (when (not (eq n-lang lang))
+      (error "The language must be the same"))
+    (let* ((nr (cdr (car new-rules)))
+           (r (cdr (car rules)))
+           (tmp nil)
+           (result
+            (cond
+             ((eq how :prepend)
+	      (append nr r))
+             ((eq how :append)
+              (append r nr))
+             ((or (eq how :replace) t)
+              (nreverse
+               (progn
+                 (dolist (new-rule nr)
+	           (dolist (rule r)
+	             (if (equal (nth 0 new-rule) (nth 0 rule))
+		         (push new-rule tmp)
+		       (push rule tmp))))
+                 tmp))))))
+      (push lang result)
+      `(,result))))
+
 ;;; Search
 
 (defun treesit-search-forward-goto
-- 
2.47.1


--nextPart4608876.niJfEyVGOH--







Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.
Severity set to 'wishlist' from 'normal' Request was from Stefan Kangas <stefankangas@HIDDEN> to control <at> debbugs.gnu.org. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 24 Dec 2024 22:20:00 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Dec 24 17:20:00 2024
Received: from localhost ([127.0.0.1]:35227 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tQDFv-0002OS-Nm
	for submit <at> debbugs.gnu.org; Tue, 24 Dec 2024 17:20:00 -0500
Received: from mail-pl1-f177.google.com ([209.85.214.177]:58725)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <casouri@HIDDEN>) id 1tQDFt-0002OA-8M
 for 74610 <at> debbugs.gnu.org; Tue, 24 Dec 2024 17:19:58 -0500
Received: by mail-pl1-f177.google.com with SMTP id
 d9443c01a7336-2165448243fso67972805ad.1
 for <74610 <at> debbugs.gnu.org>; Tue, 24 Dec 2024 14:19:57 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1735078731; x=1735683531; darn=debbugs.gnu.org;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:from:to:cc:subject:date
 :message-id:reply-to;
 bh=8Am/q0QyLJsGS4sCQcW9woyDyO/BMzr7YXSszkoN60k=;
 b=GwctWjCMS2YBdgInbJW3HP+v92oApmx+q4z1APXvILC9tDlO1+9N79KkcmbonZ8abx
 vFsCwRzdba+u+bX0zc2r4D+JbyW8t8Cy3dsJ20cx2FjRbZ9h5hwAn1A9PqTbRC/VP0j6
 cs9tYEJ18sum9uAcDJpWpydRm44hHl+aHdP08/tUxBDE28pqtQwpKd4DMA6tB1EznI8q
 nElCphMjxgRGTFKBF/8r8sGkUko4s1SzujK9K/yTpYqtpa/gX/gggbOorATzQFJmjZCg
 u1nGB1eYIBbu/3cosSan+AylL6RrdCUCg5l3QpwOPjUGaZWon7FriVM6lYWtLipv/QBQ
 45WQ==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1735078731; x=1735683531;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=8Am/q0QyLJsGS4sCQcW9woyDyO/BMzr7YXSszkoN60k=;
 b=XtNAxRMCTqlydNUEfXJCj4wootSQbU1n+vvCPN+TFKGyh8Soapt79YXvISjaeYS4PU
 Gw8GfCyeIDhFrk1FacRnTsGFmG9kC+QUUVqML9TGFfd6pPkAkwf/SVcrmkqwDQXG8qiP
 VFLvdDk71kbdvGEio1aHrvCJPVF7Fox38qlmgXI1JJlJOG98SVbvV8zyJfu16JaSxHNU
 AD3PJQpLcEVEDkKeQplXeUmy6GXo8mKgdN1pZKAVMZOPSHcWSt4tDJ2blbapJk4qXc8V
 rmZFhHi+9QfyYLfoNL13H/DFIAvCbYLDiu6OD/hCcJ6M04HanxF0izDx24yVJJcPc0AS
 1wmQ==
X-Gm-Message-State: AOJu0Yzzf41hyIy1DBjVRTTPiSAMigTqjmwMcyyuhVD5liUYCgNc7p73
 Qz0Xkn28juHEFSzadb65wMUPevPu7yi+5X/pC9k3AguusV5eFnkn
X-Gm-Gg: ASbGncsgNP+6nN6gDxK+dD6M20ZKObjgsJhamPLNS7ThyKnASetBD5IMSlsmxb9fIbq
 81MqKZcRYlBUu25+AOkxDbXaVxeuBb/CCADtTWufKAOHCkhca/SCJ8EZutbAsM8yCvjm6MW67cH
 FR+0aI/CIyL8PYeJ/BwNJv+MUi45APO5oyhpUqZF8F+dmrrsomCrOau57fq531642A5H4EsiiES
 Zz4Mbyc312/J6MPbMF+9jzkIywvobng9m+mH5Zrf7Rfq8W6ZoCamwpA0lL4MoVSZ5UNjrA58dXK
 mzG/
X-Google-Smtp-Source: AGHT+IEh1SdUi1zTqJH8F/meVMfe5YT6kDL4RzqYX0MWeU0YzLC5kLCouQhwZpDSm/4jzDL4AriqJg==
X-Received: by 2002:a17:902:ec88:b0:216:57a6:28b5 with SMTP id
 d9443c01a7336-219e6f3ab09mr251541995ad.56.1735078731195; 
 Tue, 24 Dec 2024 14:18:51 -0800 (PST)
Received: from smtpclient.apple ([2601:646:8f81:6120:1d98:6810:9846:b152])
 by smtp.gmail.com with ESMTPSA id
 d9443c01a7336-219dca02ac2sm94298545ad.272.2024.12.24.14.18.50
 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);
 Tue, 24 Dec 2024 14:18:50 -0800 (PST)
Content-Type: text/plain;
	charset=utf-8
Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3776.700.51\))
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
From: Yuan Fu <casouri@HIDDEN>
In-Reply-To: <018B485B-8F4D-4472-BD21-384C7EC9E31C@HIDDEN>
Date: Tue, 24 Dec 2024 14:18:39 -0800
Content-Transfer-Encoding: quoted-printable
Message-Id: <D06621B4-736C-4729-A8E4-3D5885F498F4@HIDDEN>
References: <3532547.LZWGnKmheA@fedora>
 <2551656.XAFRqVoOGU@HIDDEN>
 <0083FFEF-128F-4C5C-B6BC-92CF657416C4@HIDDEN> <26683715.1r3eYUQgxm@fedora>
 <018B485B-8F4D-4472-BD21-384C7EC9E31C@HIDDEN>
To: Vincenzo Pupillo <v.pupillo@HIDDEN>
X-Mailer: Apple Mail (2.3776.700.51)
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)



> On Dec 24, 2024, at 12:37=E2=80=AFAM, Yuan Fu <casouri@HIDDEN> =
wrote:
>=20
>=20
>=20
>> On Dec 14, 2024, at 2:37=E2=80=AFAM, Vincenzo Pupillo =
<v.pupillo@HIDDEN> wrote:
>>=20
>> In data mercoled=C3=AC 11 dicembre 2024 05:54:09 Ora standard =
dell=E2=80=99Europa centrale,=20
>> Yuan Fu ha scritto:
>>>> On Dec 3, 2024, at 6:29=E2=80=AFAM, Vincenzo Pupillo =
<v.pupillo@HIDDEN> wrote:
>>>>=20
>>>> In data domenica 1 dicembre 2024 07:01:21 Ora standard =
dell=E2=80=99Europa
>>>> centrale,>=20
>>>> Yuan Fu ha scritto:
>>>>> It's not uncommon to see different indent offset for CSS and
>>>>> Javascript, so it's a good idea to have separate control for them.
>>>>=20
>>>> Is the behavior the same as mhtml-mode, or would you like something =
like
>>>> this?>=20
>>>>  <style>
>>>>=20
>>>>                                z {
>>>>=20
>>>>                                    color: red;
>>>>=20
>>>>                                }
>>>>=20
>>>>  </style>
>>>>  <script>
>>>>=20
>>>>      function myFunction(p1, p2) {
>>>>=20
>>>>          return p1 * p2;
>>>>=20
>>>>      }
>>>>=20
>>>>  </script>
>>>>=20
>>>> The mhtml-ts-mode-js-css-indent-offset variable controls only the
>>>> indentation relative to the <style> and <script> tags.
>>>=20
>>> Ah, I see, it=E2=80=99s the offset from the enclosing tag. In that =
case it should be
>>> fine to use a common variable.
>>>=20
>>> Yuan
>> Thank you Yuan.
>> Attached is the revised patch following your previous comments.
>> As I already wrote to Dmitry, I am doing some tests to see if =
html-ts-mode can=20
>> be extended and if there is a way to integrate one multi-language =
mode into=20
>> another multi-language mode.
>>=20
>> Vincenzo
>> <0001-Add-mhtml-ts-mode.patch>
>=20
> Btw, mhtml-ts-mode--defun-name seems to contain some debugging code? =
And also I think you should use treesit-node-language.
>=20
> Yuan

I just added treesit-aggregated-simple-imenu-settings to master. This =
variable will allow you to setup Imenu for multiple languages. Please =
give it a try. I tested locally with mhtml-ts-mode and works well.

Yuan=




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 24 Dec 2024 08:38:24 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Dec 24 03:38:24 2024
Received: from localhost ([127.0.0.1]:59263 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tQ0Qq-0002Oi-BD
	for submit <at> debbugs.gnu.org; Tue, 24 Dec 2024 03:38:24 -0500
Received: from mail-pj1-f41.google.com ([209.85.216.41]:60545)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <casouri@HIDDEN>) id 1tQ0Qn-0002OU-Te
 for 74610 <at> debbugs.gnu.org; Tue, 24 Dec 2024 03:38:22 -0500
Received: by mail-pj1-f41.google.com with SMTP id
 98e67ed59e1d1-2f4448bf96fso3825208a91.0
 for <74610 <at> debbugs.gnu.org>; Tue, 24 Dec 2024 00:38:21 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1735029435; x=1735634235; darn=debbugs.gnu.org;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:from:to:cc:subject:date
 :message-id:reply-to;
 bh=WLlVSPr84m0qRbfA235BS0PMBCF/bx28Tzgw6TdYWFE=;
 b=mXJ6+o3ce1D+gtHGxgRZ20PHTqMzC3AsqN1KoBA9dKk/bizvQU7BalJYKzqRMYstNf
 1XZzK2LTvL0UnQyVbiSfjvA4z7y5UMFfuYMRC9VT4xsqB+EcaPDE/pxadP5KNCjCZ+2S
 0QU1+m94M1qiypk1t12ZNzmBSxF7ndXeECiduc9LPqgcQBzHomkrJv7uwRnTQR+qYg3N
 PP0YAFlX+MAlB2OxF6HTfqBKGM1Gvc4pVuhwr3tm4m+l7MFAxPeNJaBc3547UTq3eNIP
 1lBb4Lxc4KIzI/t4y5EOjRuvwR05xOZsgXCtoGHT++1muQdVXBKI92fXtmy0dFxKP7vR
 hRow==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1735029435; x=1735634235;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=WLlVSPr84m0qRbfA235BS0PMBCF/bx28Tzgw6TdYWFE=;
 b=RTCq853+V0E6zDaWKUUs70zNPUkypd0D+9ObFU0Geq63Zdfa1AxMFHjd5X06oq7b3F
 Zp+X8i7aj63QQBcOHGEpSysbsyNNklgxXiDBEnBX6VNe3bmQvGTDr2iU2djgGKansSde
 7ZQk6AHGepPY2oxl4zmfGri4IZQ8zmB5iqfsfXB2bsHCwwtSKQL6deNs0AoCWRpmk1lT
 pfF+N8sVHRJXZy/S6Gpgye/JKc305Ia/jUaXKJRNxAEwvtosTzmCxK6n/vSfZxBtJ6Fg
 H9xTuGj0wyrzXioT3DefVMEZ3uFOBcShDEfAJKfhPWJjrlM2G8zI3eKmWYiQfqAKzZo5
 WVGQ==
X-Gm-Message-State: AOJu0YwN7Ykl3ewfuC4OyD3lyztZqIOJAdB2oKxI98AaGad2YfQ6ji/w
 /96Fy3iVjrE+Q6z1ZkhiNUnkXe9z+BZcaq+dD8Hq4yBI3M8RxFr2
X-Gm-Gg: ASbGncvyRBL6VdN6b5AYVzULGIkiVZMsH78MgtJMytzRlG/JCAgFK6jj2xwFYzupPP5
 ycoqMK8pRkRJCpIX6QZ4ev1atftN5E3hPANKbZYn0EazGy6POJAS4RUrla6gkCwbeTksKnAfFgh
 lu9GmoB1GZ9PVhQnwHwr47qr6ubVF3ByI9JW4A/rX882mxxqbE/w1JlysKNyA4tpoct+CHIY+uf
 aEi04IohNbEppiLdjHa6FjJNs1UqIQhdcggCN9GP6MgQGYyk4Ifv0J1E3UhIldzflm/jhUC/pj1
 QLSC
X-Google-Smtp-Source: AGHT+IEmSyJZSqL7r3RNoGzm0W7B+Qbek4zUOYdjEZZY4qkcd9ReZXDRrW6dWH419w35NyjAPjPqbw==
X-Received: by 2002:a17:90b:3a43:b0:2ee:45fe:63b5 with SMTP id
 98e67ed59e1d1-2f452debbd9mr20859648a91.3.1735029434825; 
 Tue, 24 Dec 2024 00:37:14 -0800 (PST)
Received: from smtpclient.apple ([2601:646:8f81:6120:1d98:6810:9846:b152])
 by smtp.gmail.com with ESMTPSA id
 98e67ed59e1d1-2f447883b0csm10973284a91.42.2024.12.24.00.37.13
 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);
 Tue, 24 Dec 2024 00:37:14 -0800 (PST)
Content-Type: text/plain;
	charset=utf-8
Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3776.700.51\))
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
From: Yuan Fu <casouri@HIDDEN>
In-Reply-To: <26683715.1r3eYUQgxm@fedora>
Date: Tue, 24 Dec 2024 00:37:02 -0800
Content-Transfer-Encoding: quoted-printable
Message-Id: <018B485B-8F4D-4472-BD21-384C7EC9E31C@HIDDEN>
References: <3532547.LZWGnKmheA@fedora>
 <2551656.XAFRqVoOGU@HIDDEN>
 <0083FFEF-128F-4C5C-B6BC-92CF657416C4@HIDDEN> <26683715.1r3eYUQgxm@fedora>
To: Vincenzo Pupillo <v.pupillo@HIDDEN>
X-Mailer: Apple Mail (2.3776.700.51)
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)



> On Dec 14, 2024, at 2:37=E2=80=AFAM, Vincenzo Pupillo =
<v.pupillo@HIDDEN> wrote:
>=20
> In data mercoled=C3=AC 11 dicembre 2024 05:54:09 Ora standard =
dell=E2=80=99Europa centrale,=20
> Yuan Fu ha scritto:
>>> On Dec 3, 2024, at 6:29=E2=80=AFAM, Vincenzo Pupillo =
<v.pupillo@HIDDEN> wrote:
>>>=20
>>> In data domenica 1 dicembre 2024 07:01:21 Ora standard dell=E2=80=99Eu=
ropa
>>> centrale,>=20
>>> Yuan Fu ha scritto:
>>>> It's not uncommon to see different indent offset for CSS and
>>>> Javascript, so it's a good idea to have separate control for them.
>>>=20
>>> Is the behavior the same as mhtml-mode, or would you like something =
like
>>> this?>=20
>>>   <style>
>>>=20
>>>                                 z {
>>>=20
>>>                                     color: red;
>>>=20
>>>                                 }
>>>=20
>>>   </style>
>>>   <script>
>>>=20
>>>       function myFunction(p1, p2) {
>>>=20
>>>           return p1 * p2;
>>>=20
>>>       }
>>>=20
>>>   </script>
>>>=20
>>> The mhtml-ts-mode-js-css-indent-offset variable controls only the
>>> indentation relative to the <style> and <script> tags.
>>=20
>> Ah, I see, it=E2=80=99s the offset from the enclosing tag. In that =
case it should be
>> fine to use a common variable.
>>=20
>> Yuan
> Thank you Yuan.
> Attached is the revised patch following your previous comments.
> As I already wrote to Dmitry, I am doing some tests to see if =
html-ts-mode can=20
> be extended and if there is a way to integrate one multi-language mode =
into=20
> another multi-language mode.
>=20
> Vincenzo
> <0001-Add-mhtml-ts-mode.patch>

Btw, mhtml-ts-mode--defun-name seems to contain some debugging code? And =
also I think you should use treesit-node-language.

Yuan=




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 17 Dec 2024 21:27:17 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Dec 17 16:27:17 2024
Received: from localhost ([127.0.0.1]:60565 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tNf5x-0006rG-TR
	for submit <at> debbugs.gnu.org; Tue, 17 Dec 2024 16:27:17 -0500
Received: from mail-wm1-f47.google.com ([209.85.128.47]:47477)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <v.pupillo@HIDDEN>) id 1tNf5v-0006qe-67
 for 74610 <at> debbugs.gnu.org; Tue, 17 Dec 2024 16:27:07 -0500
Received: by mail-wm1-f47.google.com with SMTP id
 5b1f17b1804b1-4364a37a1d7so12872665e9.3
 for <74610 <at> debbugs.gnu.org>; Tue, 17 Dec 2024 13:27:07 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1734470761; x=1735075561; darn=debbugs.gnu.org;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:from:to:cc:subject:date
 :message-id:reply-to;
 bh=ktKdTRouS/AVC2VXdIDZh5Gw04zI6UnPBncGvgYzMG4=;
 b=eEFsG8h+ghOHR52qlNYTUvE/WSwxhAxxMr8EAv2ykn9VyEyAhACLEBDDlHIe8C647R
 LWIr8e4Rzf8k3xMQflmNek6rWJze0s2n4O6dPgyobZAKm4H+QgTfxsxFxdt0qaTQgxRp
 NQ9ua+AakILiBjX5V9wuImBiby8MhrVDd+ZeyZTjsK+CIvrpKRMNl7oH63uoptlSnijf
 vSdw9rA8+v1oBW8/IW5eOmWtar399J6Y8yRaiNLV1EdLCjlErgA0BjEizv+vmnSYBjdN
 1s1HNVGn55q95dZV8CrK82H5aw33siN99AYUYK9ELOXIAB829prBqandKe+kZ/lpJW1l
 Ir0g==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1734470761; x=1735075561;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=ktKdTRouS/AVC2VXdIDZh5Gw04zI6UnPBncGvgYzMG4=;
 b=Lbi/SsueIU1mAqIYtGiDIXW5rYc3BIy7HMHqHpJXc0Ixl4KTI2AgnFMZT+GwN1zbZh
 t4FSStz1X/JiK1Oa+ViHK9sU7P83Hl7TJ9XHakvW5suoP1AUXJaYgKAkpcL1t/jwcXFX
 qORxBVVc/rkDROK0uoMSPzcuSV5QFJQ7GuFuEbM2sFwMawI46BAQtYshqtxBJTqEwGS2
 Za1M03ow/Yl+TZfEuLLhFb0kpZXNsR0FELgOLM1lQg+aGWFIjEZX8pqqAkFEp/SyXG6J
 oYRDjX6WRIOj3kElKN8hbBtRtE7DD+dtYcIHRKR5+sjJW/r5rCeYpxa/eUDAgj5kYFhm
 zsRg==
X-Forwarded-Encrypted: i=1;
 AJvYcCUVFmBdTkZDDHyZXFtEvbUK9BmhRyQUjb+UYQdo+4sMnckusbjJ/Dt3GmYL0zKdEd6YA110pA==@debbugs.gnu.org
X-Gm-Message-State: AOJu0YwWmLcPwjSJvzB2WYsxZlfMaIUR6FbanoZMHjNUx/xs3D8Dts/7
 aYi5Gkf4WDaMkVaR8GkSNCf+g63DNsxzkZ6EMi8jmFY3wjzTbAx6hzXfH8wY
X-Gm-Gg: ASbGnct75aLNIZK3wCaa07Eyqsm8tFyziI2GneKoKygopGA4VRvt/Qi3Dd603xu0FY5
 YmwD6xvfQ3An/WdUxA628Ps9r90kMRzSq2O8kvoO8QixXPgbAigqFkwHTVW9BSai3SjaCX1jIl0
 XHUDUdVajIt3TciTQ+tyGWtzSxkRCQMjB8F8vdiQ04jE7ATTFH7VY3mp/6EbverWdsQh6vpHOzl
 7ApT6ZHblISk3saRmAsw6ghAzIYMy9VIABL7pBvspmbbDQiTZ0ysCqUFK/PtqFyA5TalZo5UBKY
 2tNRyt8NjVV14ICWSK/A
X-Google-Smtp-Source: AGHT+IEFGY4xxy4+EtXBQ50KgpNwNXePXYgxXYbhRSmco57f0+ejnkMqVqci0U9EAJuDcP0f/HXdmA==
X-Received: by 2002:a05:600c:154b:b0:436:30e4:459b with SMTP id
 5b1f17b1804b1-4365536f96amr2236905e9.18.1734470761055; 
 Tue, 17 Dec 2024 13:26:01 -0800 (PST)
Received: from fedora.localnet (2-230-139-124.ip202.fastwebnet.it.
 [2.230.139.124]) by smtp.gmail.com with ESMTPSA id
 5b1f17b1804b1-43625706cd3sm187711275e9.28.2024.12.17.13.26.00
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Tue, 17 Dec 2024 13:26:00 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: Juri Linkov <juri@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Tue, 17 Dec 2024 22:25:59 +0100
Message-ID: <3637930.dWV9SEqChM@fedora>
In-Reply-To: <87r067fs0w.fsf@HIDDEN>
References: <3532547.LZWGnKmheA@fedora> <26683715.1r3eYUQgxm@fedora>
 <87r067fs0w.fsf@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: Yuan Fu <casouri@HIDDEN>, Eli Zaretskii <eliz@HIDDEN>
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Ciao Juri,

In data luned=C3=AC 16 dicembre 2024 18:37:35 Ora standard dell=E2=80=99Eur=
opa centrale,=20
Juri Linkov ha scritto:
> Ciao Vincenzo,
>=20
> > In data mercoled=C3=AC 11 dicembre 2024 05:54:09 Ora standard dell=E2=
=80=99Europa
> > centrale,
> >=20
> > Attached is the revised patch following your previous comments.
> > As I already wrote to Dmitry, I am doing some tests to see if html-ts-m=
ode
> > can be extended and if there is a way to integrate one multi-language
> > mode into another multi-language mode.
>=20
> I'm testing your patch with mhtml-ts-mode, and everything works nicely.
>=20

Thanks

> At the same time I'm adding a new ts-thing named 'sexp-list' in bug#73404.
> While 'sexp' defines both lists and atoms, 'sexp-list' defines only lists.
>=20
> So I added (sexp-list ,(regexp-opt '("element")) 'symbols)
> to treesit-thing-settings in html-ts-mode.el.
>=20
> But then discovered surprisingly that it has no effect on mhtml-ts-mode.
>=20
> The problem is that treesit-thing-settings should be duplicated
> from html-ts-mode to mhtml-ts-mode.
>=20
> On the one hand, integrating multi-language mode to the exiting mode
> html-ts-mode could avoid the need to duplicate treesit-thing-settings
> for html.
>=20
> But on the other hand, integrating mhtml-ts-mode to html-ts-mode
> doesn't help to avoid such duplication for other embedded modes.
> Because I needed to duplicate treesit-thing-settings for javascript
> as well.
>=20
> So extending html-ts-mode doesn't help here, and maybe even better
> to add mhtml-ts-mode to keep the symmetry with existing mhtml-mode
> such as used in mode remapping:
>=20
>   (add-to-list 'major-mode-remap-alist '(mhtml-mode .
>                                          mhtml-ts-mode))
>=20
> What could really help is to try to get settings from html-ts-mode
> and js-ts-mode to avoid the need to duplicate settings in mhtml-ts-mode.

I think we need something like a generalized version of the=20
`mhtml--construct-submode' function. I'm doing some testing on that and hop=
e=20
to have something decent after Christmas.

Vincenzo.








Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 16 Dec 2024 17:45:30 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Mon Dec 16 12:45:29 2024
Received: from localhost ([127.0.0.1]:55959 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tNF9t-0007PO-8Y
	for submit <at> debbugs.gnu.org; Mon, 16 Dec 2024 12:45:29 -0500
Received: from relay8-d.mail.gandi.net ([217.70.183.201]:37801)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <juri@HIDDEN>) id 1tNF9m-0007Oc-IX
 for 74610 <at> debbugs.gnu.org; Mon, 16 Dec 2024 12:45:23 -0500
Received: by mail.gandi.net (Postfix) with ESMTPSA id D62A01BF207;
 Mon, 16 Dec 2024 17:44:55 +0000 (UTC)
From: Juri Linkov <juri@HIDDEN>
To: Vincenzo Pupillo <v.pupillo@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
In-Reply-To: <26683715.1r3eYUQgxm@fedora> (Vincenzo Pupillo's message of "Sat, 
 14 Dec 2024 11:37:01 +0100")
Organization: LINKOV.NET
References: <3532547.LZWGnKmheA@fedora>
 <2551656.XAFRqVoOGU@HIDDEN>
 <0083FFEF-128F-4C5C-B6BC-92CF657416C4@HIDDEN>
 <26683715.1r3eYUQgxm@fedora>
Date: Mon, 16 Dec 2024 19:37:35 +0200
Message-ID: <87r067fs0w.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/31.0.50 (x86_64-pc-linux-gnu)
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
X-GND-Sasl: juri@HIDDEN
X-Spam-Score: -0.7 (/)
X-Debbugs-Envelope-To: 74610
Cc: Yuan Fu <casouri@HIDDEN>, Eli Zaretskii <eliz@HIDDEN>,
 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.7 (-)

Ciao Vincenzo,

> In data mercoledì 11 dicembre 2024 05:54:09 Ora standard dell’Europa centrale, 
>
> Attached is the revised patch following your previous comments.
> As I already wrote to Dmitry, I am doing some tests to see if html-ts-mode can 
> be extended and if there is a way to integrate one multi-language mode into 
> another multi-language mode.

I'm testing your patch with mhtml-ts-mode, and everything works nicely.

At the same time I'm adding a new ts-thing named 'sexp-list' in bug#73404.
While 'sexp' defines both lists and atoms, 'sexp-list' defines only lists.

So I added (sexp-list ,(regexp-opt '("element")) 'symbols)
to treesit-thing-settings in html-ts-mode.el.

But then discovered surprisingly that it has no effect on mhtml-ts-mode.

The problem is that treesit-thing-settings should be duplicated
from html-ts-mode to mhtml-ts-mode.
                 
On the one hand, integrating multi-language mode to the exiting mode
html-ts-mode could avoid the need to duplicate treesit-thing-settings
for html.

But on the other hand, integrating mhtml-ts-mode to html-ts-mode
doesn't help to avoid such duplication for other embedded modes.
Because I needed to duplicate treesit-thing-settings for javascript
as well.

So extending html-ts-mode doesn't help here, and maybe even better
to add mhtml-ts-mode to keep the symmetry with existing mhtml-mode
such as used in mode remapping:

  (add-to-list 'major-mode-remap-alist '(mhtml-mode .
                                         mhtml-ts-mode))

What could really help is to try to get settings from html-ts-mode
and js-ts-mode to avoid the need to duplicate settings in mhtml-ts-mode.




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 14 Dec 2024 10:38:09 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sat Dec 14 05:38:09 2024
Received: from localhost ([127.0.0.1]:45648 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tMPXE-0001SG-1W
	for submit <at> debbugs.gnu.org; Sat, 14 Dec 2024 05:38:09 -0500
Received: from mail-wm1-f51.google.com ([209.85.128.51]:45471)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <v.pupillo@HIDDEN>) id 1tMPXB-0001S7-Fb
 for 74610 <at> debbugs.gnu.org; Sat, 14 Dec 2024 05:38:06 -0500
Received: by mail-wm1-f51.google.com with SMTP id
 5b1f17b1804b1-4361c705434so18202635e9.3
 for <74610 <at> debbugs.gnu.org>; Sat, 14 Dec 2024 02:38:05 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1734172625; x=1734777425; darn=debbugs.gnu.org;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:from:to:cc:subject:date
 :message-id:reply-to;
 bh=VlrrXP43XJoyjlIAN7jB4uXqtH1wDYO+bF/cSX1vv0A=;
 b=C6/wxdnB40Yy7XvKJ1K1hcat+O3IPLZYLSPaSeYscK5wDlMlqfE28hFCIEVl7jHbuU
 v3fuksXA3+sDbb2yc/DedRiSnbit8Lf5gp7JcAWLCVvu4AMedJ6ZdMinshTfe3B9qP+A
 0dZTq08u5pnuSWsDIib4wf3sKU4CyejLYwysngHT4hwMhtdanNUJ47JFsDHO0H0PcwoP
 cUhROPK+cxlMBHkWCaeC8lYOvWvTH+n2F2e6gd+IHD5jwH4dqBSykQMtvFOFe078jPti
 uWJ2WyNgwpLs0v/IKh95Aga+SqFAGun7bLXehIx9J1eMHFIisXZ0NzRu8rxbTqkoEbha
 QzDA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1734172625; x=1734777425;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=VlrrXP43XJoyjlIAN7jB4uXqtH1wDYO+bF/cSX1vv0A=;
 b=q+hEJhBoqe2iC5pyY+UH1KIx/3rAE1zTZmhKuES5S7V9v5pSPGb+9SAdh7uvtegF+F
 8lLCLYy2vQaB7hkirEjmEu1V0rI06qbd8WZQO4v6DzlEKxZewRZds2b9uvO1g6QdAWck
 p8dFKJ9dH8nP9OWvquWgA8RZinXdipnpnwdo8a30TbmsMsGzaeeLaVRx/xP/ira7Hhaa
 6h0kyd/4NjeBfiN26D3/GQ7t3PaFvG5HaRfESt5qK9FXw0BL20BNc5YlOeMwPzHqOGk/
 Fb4r/ue3bWB60yE5aJ5blbGkktJmLMdJSFCpowuhPo4Wra7OC4PiTJMDPKOinfVCNy3x
 iKZw==
X-Gm-Message-State: AOJu0YyEKQVut2uUy5uPvv7ENy1lero1XdRNsYX8Klo4v8o6Wp5XfaBc
 EBNwC8ILuzXtmEWJ2/X3YX9aZoxOcus34/p1MhFZQlNfdgny1nNf
X-Gm-Gg: ASbGncsn+jOQbcepHhvl2ledRH6+TW0u5e4gO4pOL4d6vWvtEzlRwl3gBC4EFP0JAhK
 eRvv6DlzFf5H8KLDoBdez610IyjydZMHfu2b9h5lQ4HbdFk/YxZZq68s5UthQsgjdr0QZ0D04tS
 3YGTV21O4uHzAxsMkQW9GIP0rbhffxGDPvPFbuSxTdCZqS5NAH2G2gpDGs2gyvN/hn+E8Zbv9MH
 5k0cNEUKgQY3J085b1Jfcl+aP3rrXiHDOfQvgXZNviwKSG5vuiWt6wQ9ANgM8PQM60wOUPHxSA8
 JGQt7uwULwcIPGgURXsU
X-Google-Smtp-Source: AGHT+IG4nQGYC3iWX+10zrpgRCOnwEG/HF9sWVjXlRk2UowdXcDeUxtlADJRpMHhaDa3m4P/D5s6XA==
X-Received: by 2002:a05:6000:4613:b0:385:ed16:c8b with SMTP id
 ffacd0b85a97d-38880ad1d9emr4595714f8f.23.1734172624201; 
 Sat, 14 Dec 2024 02:37:04 -0800 (PST)
Received: from fedora.localnet (2-230-139-124.ip202.fastwebnet.it.
 [2.230.139.124]) by smtp.gmail.com with ESMTPSA id
 ffacd0b85a97d-388c8046f8bsm2167450f8f.68.2024.12.14.02.37.02
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Sat, 14 Dec 2024 02:37:03 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: Yuan Fu <casouri@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Sat, 14 Dec 2024 11:37:01 +0100
Message-ID: <26683715.1r3eYUQgxm@fedora>
In-Reply-To: <0083FFEF-128F-4C5C-B6BC-92CF657416C4@HIDDEN>
References: <3532547.LZWGnKmheA@fedora>
 <2551656.XAFRqVoOGU@HIDDEN>
 <0083FFEF-128F-4C5C-B6BC-92CF657416C4@HIDDEN>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="nextPart2282944.vFx2qVVIhK"
Content-Transfer-Encoding: 7Bit
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

This is a multi-part message in MIME format.

--nextPart2282944.vFx2qVVIhK
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"

In data mercoled=C3=AC 11 dicembre 2024 05:54:09 Ora standard dell=E2=80=99=
Europa centrale,=20
Yuan Fu ha scritto:
> > On Dec 3, 2024, at 6:29=E2=80=AFAM, Vincenzo Pupillo <v.pupillo@HIDDEN=
om> wrote:
> >=20
> > In data domenica 1 dicembre 2024 07:01:21 Ora standard dell=E2=80=99Eur=
opa
> > centrale,>=20
> > Yuan Fu ha scritto:
> >> It's not uncommon to see different indent offset for CSS and
> >> Javascript, so it's a good idea to have separate control for them.
> >=20
> > Is the behavior the same as mhtml-mode, or would you like something like
> > this?>=20
> >    <style>
> >   =20
> >                                  z {
> >                                 =20
> >                                      color: red;
> >                                 =20
> >                                  }
> >   =20
> >    </style>
> >    <script>
> >   =20
> >        function myFunction(p1, p2) {
> >       =20
> >            return p1 * p2;
> >       =20
> >        }
> >   =20
> >    </script>
> >=20
> > The mhtml-ts-mode-js-css-indent-offset variable controls only the
> > indentation relative to the <style> and <script> tags.
>=20
> Ah, I see, it=E2=80=99s the offset from the enclosing tag. In that case i=
t should be
> fine to use a common variable.
>=20
> Yuan
Thank you Yuan.
Attached is the revised patch following your previous comments.
As I already wrote to Dmitry, I am doing some tests to see if html-ts-mode =
can=20
be extended and if there is a way to integrate one multi-language mode into=
=20
another multi-language mode.

Vincenzo

--nextPart2282944.vFx2qVVIhK
Content-Disposition: attachment; filename="0001-Add-mhtml-ts-mode.patch"
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-patch; charset="UTF-8";
 name="0001-Add-mhtml-ts-mode.patch"

=46rom 355075793eff5a58dac83756d96881b6932d5838 Mon Sep 17 00:00:00 2001
=46rom: Vincenzo Pupillo <v.pupillo@HIDDEN>
Date: Fri, 29 Nov 2024 22:48:45 +0100
Subject: [PATCH] Add mhtml-ts-mode.

New major-mode alternative to mhtml-mode, based on treesitter, for
editing files containing html, javascript and css.

* etc/NEWS: Mention the new mode.
* lisp/textmodes/mhtml-ts-mode.el: New file.
=2D--
 etc/NEWS                        |   8 +
 lisp/textmodes/mhtml-ts-mode.el | 429 ++++++++++++++++++++++++++++++++
 2 files changed, 437 insertions(+)
 create mode 100644 lisp/textmodes/mhtml-ts-mode.el

diff --git a/etc/NEWS b/etc/NEWS
index 4d2a2c893d0..8f9a04dcf01 100644
=2D-- a/etc/NEWS
+++ b/etc/NEWS
@@ -797,6 +797,14 @@ destination window is chosen using 'display-buffer-ali=
st'.  Example:
 =0C
 * New Modes and Packages in Emacs 31.1
=20
+** New major modes based on the tree-sitter library
+
++++
+*** New major mode 'mhtml-ts-mode'.
+An optional major mode based on the tree-sitter library for editing html
+files. This mode handles indentation, fontification, and commenting for
+embedded JavaScript and CSS.
+
 =0C
 * Incompatible Lisp Changes in Emacs 31.1
=20
diff --git a/lisp/textmodes/mhtml-ts-mode.el b/lisp/textmodes/mhtml-ts-mode=
=2Eel
new file mode 100644
index 00000000000..746300efc33
=2D-- /dev/null
+++ b/lisp/textmodes/mhtml-ts-mode.el
@@ -0,0 +1,429 @@
+;;; mhtml-ts-mode.el --- Major mode for HTML using tree-sitter -*- lexical=
=2Dbinding: t; -*-
+
+;; Copyright (C) 2024 Free Software Foundation, Inc.
+
+;; Author: Vincenzo Pupillo <v.pupillo@HIDDEN>
+;; Maintainer: Vincenzo Pupillo <v.pupillo@HIDDEN>
+;; Created: Nov 2024
+;; Keywords: HTML language tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; This package provides `mhtml-ts-mode' which is a major mode
+;; for editing HTML files with embedded JavaScript and CSS.
+;; Tree Sitter is used to parse each of these languages.
+;;
+;; Please note that this package requires `html-ts-mode', which
+;; registers itself as the major mode for editing HTML.
+;;
+;; This package is compatible and has been tested with the following
+;; tree-sitter grammars:
+;; * https://github.com/tree-sitter/tree-sitter-html
+;; * https://github.com/tree-sitter/tree-sitter-javascript
+;; * https://github.com/tree-sitter/tree-sitter-jsdoc
+;; * https://github.com/tree-sitter/tree-sitter-css
+;;
+;; Features
+;;
+;; * Indent
+;; * IMenu
+;; * Navigation
+;; * Which-function
+;; * Tree-sitter parser installation helper
+
+;;; Code:
+
+(require 'treesit)
+(require 'html-ts-mode)
+(require 'css-mode) ;; for embed css into html
+(require 'js) ;; for embed javascript into html
+
+(eval-when-compile
+  (require 'rx))
+
+;; This tells the byte-compiler where the functions are defined.
+;; Is only needed when a file needs to be able to byte-compile
+;; in a Emacs not built with tree-sitter library.
+(treesit-declare-unavailable-functions)
+
+;; In a multi-language major mode can be useful to have an "installer" to
+;; simplify the installation of the grammars supported by the major-mode.
+(defvar mhtml-ts-mode--language-source-alist
+  '((html . ("https://github.com/tree-sitter/tree-sitter-html"  "v0.23.0"))
+    (javascript . ("https://github.com/tree-sitter/tree-sitter-javascript"=
 "v0.23.0"))
+    (jsdoc . ("https://github.com/tree-sitter/tree-sitter-jsdoc" "v0.23.0"=
))
+    (css . ("https://github.com/tree-sitter/tree-sitter-css" "v0.23.0")))
+  "Treesitter language parsers required by `mhtml-ts-mode'.
+You can customize this variable if you want to stick to a specific
+commit and/or use different parsers.")
+
+(defun mhtml-ts-mode-install-parsers ()
+  "Install all the required treesitter parsers.
+`mhtml-ts-mode--language-source-alist' defines which parsers to install."
+  (interactive)
+  (let ((treesit-language-source-alist mhtml-ts-mode--language-source-alis=
t))
+    (dolist (item mhtml-ts-mode--language-source-alist)
+      (treesit-install-language-grammar (car item)))))
+
+;;; Custom variables
+
+(defgroup mhtml-ts-mode nil
+  "Major mode for editing HTML files, based on `html-ts-mode'.
+Works with JS and CSS and for that use `js-ts-mode' and `css-ts-mode'."
+  :prefix "mhtml-ts-mode-"
+  ;; :group 'languages
+  :group 'html)
+
+(defcustom mhtml-ts-mode-js-css-indent-offset 2
+  "JavaScript and CSS indent spaces related to the <script> and <style> HT=
ML tags.
+By default should have same value as `html-ts-mode-indent-offset'."
+  :tag "HTML javascript or css indent offset"
+  :version "31.1"
+  :type 'integer
+  :safe 'integerp)
+
+(defvar mhtml-ts-mode--js-css-indent-offset
+  mhtml-ts-mode-js-css-indent-offset
+  "Internal copy of `mhtml-ts-mode-js-css-indent-offset'.
+The value changes, by `mhtml-ts-mode--tag-relative-indent-offset' accordin=
g to
+the value of `mhtml-ts-mode-tag-relative-indent'.")
+
+(defun mhtml-ts-mode--tag-relative-indent-offset (sym val)
+  "Custom setter for `mhtml-ts-mode-tag-relative-indent'.
+
+Apart from setting the default value of SYM to VAL, also change the
+value of SYM in `mhtml-ts-mode' buffers to VAL.  SYM should be
+`mhtml-ts-mode-tag-relative-indent', and VAL should be t, nil or
+`ignore'.  When sym is `mhtml-ts-mode-tag-relative-indent' set the
+value of `mhtml-ts-mode--js-css-indent-offset' to 0 if VAL is t,
+otherwise to `mhtml-ts-mode-js-css-indent-offset'."
+  (set-default sym val)
+  (when (eq sym 'mhtml-ts-mode-tag-relative-indent)
+    (setq-local
+     mhtml-ts-mode--js-css-indent-offset
+     (if (eq val t)
+         mhtml-ts-mode-js-css-indent-offset
+       0))))
+
+(defcustom mhtml-ts-mode-tag-relative-indent t
+  "How <script> and <style> bodies are indented relative to the tag.
+
+When t, indentation looks like:
+
+  <script>
+    code();
+  </script>
+
+When nil, indentation of the script body starts just below the
+tag, like:
+
+  <script>
+  code();
+  </script>
+
+When `ignore', the script body starts in the first column, like:
+
+  <script>
+code();
+  </script>"
+  :type '(choice (const nil) (const t) (const ignore))
+  :safe 'symbolp
+  :set #'mhtml-ts-mode--tag-relative-indent-offset
+  :version "31.1")
+
+(defcustom mhtml-ts-mode-css-fontify-colors t
+  "Whether CSS colors should be fontified using the color as the backgroun=
d.
+If non-nil, text representing a CSS color will be fontified
+such that its background is the color itself.
+Works like `css--fontify-region'."
+  :tag "HTML colors the CSS properties values."
+  :version "31.1"
+  :type 'boolean
+  :safe 'booleanp)
+
+;; To enable some basic treesiter functionality, you should define
+;; a function that recognizes which grammar is used at-point.
+;; This function should be assigned to `treesit-language-at-point-function'
+(defun mhtml-ts-mode--language-at-point (point)
+  "Return the language at POINT assuming the point is within a HTML buffer=
=2E"
+  (let* ((node (treesit-node-at point 'html))
+         (parent (treesit-node-parent node))
+         (node-query (format "(%s (%s))"
+                             (treesit-node-type parent)
+                             (treesit-node-type node))))
+    (cond
+     ((string-equal "(script_element (raw_text))" node-query) 'javascript)
+     ((string-equal "(style_element (raw_text))" node-query) 'css)
+     (t 'html))))
+
+;; Custom font-lock function that's used to apply color to css color
+;; The signature of the function should be conforming to signature
+;; QUERY-SPEC required by `treesit-font-lock-rules'.
+(defun mhtml-ts-mode--colorize-css-value (node override start end &rest _)
+  "Colorize CSS property value like `css--fontify-region'.
+For NODE, OVERRIDE, START, and END, see `treesit-font-lock-rules'."
+  (if (and mhtml-ts-mode-css-fontify-colors
+           (string-equal "plain_value" (treesit-node-type node)))
+      (let ((color (css--compute-color start (treesit-node-text node t))))
+        (when color
+          (with-silent-modifications
+            (add-text-properties
+             (treesit-node-start node) (treesit-node-end node)
+             (list 'face (list :background color
+                               :foreground (readable-foreground-color
+                                            color)
+                               :box '(:line-width -1)))))))
+    (treesit-fontify-with-override
+     (treesit-node-start node) (treesit-node-end node)
+     'font-lock-variable-name-face
+     override start end)))
+
+;; Embedded languages =E2=80=8B=E2=80=8Bshould be indented according to th=
e language
+;; that embeds them.
+;; This function signature complies with `treesit-simple-indent-rules'
+;; ANCHOR.
+(defun mhtml-ts-mode--js-css-tag-bol (_node _parent &rest _)
+  "Find the first non-space characters of html tags <script> or <style>.
+Return `line-beginning-position' when `treesit-node-at' is html, or
+`mhtml-ts-mode-tag-relative-indent' is equal to ignore.
+NODE and PARENT are ignored."
+  (if (or (eq (treesit-language-at (point)) 'html)
+          (eq mhtml-ts-mode-tag-relative-indent 'ignore))
+      (line-beginning-position)
+    ;; Ok, we are in js or css block.
+    (save-excursion
+      (re-search-backward "<script.*>\\|<style.*>" nil t))))
+
+;; Treesit supports 4 level of decoration, `treesit-font-lock-level'
+;; define which level to use.  Major modes categorize their fontification
+;; features, these categories are defined by `treesit-font-lock-rules' of
+;; each major-mode using :feature keyword.
+;; In a multiple language Major mode it's a good idea to provide, for each
+;; level, the union of the :feature of the same level.
+(defvar mhtml-ts-mode--feature-list
+  '(;; level 1
+    (;; common
+     comment definition
+     ;; JS specific
+     document
+     ;; CSS specific
+     query selector)
+    ;; level 2
+    (keyword name property string type)
+    ;; level 3
+    (;; common
+     attribute assignment constant escape-sequence
+     base-clause literal variable-name variable
+     ;; Javascript specific
+     jsx number pattern string-interpolation)
+    ;; level 4
+    (bracket delimiter error operator function)))
+
+;; In order to support `which-fuction-mode' we should define
+;; a function that return the defun name.
+;; In a multilingual treesit mode, this can be implemented simply by
+;; calling language-specific functions.
+(defun mhtml-ts-mode--defun-name (node)
+  "Return the defun name of NODE.
+Return nil if there is no name or if NODE is not a defun node."
+  (let ((lang (mhtml-ts-mode--language-at-point (point))))
+    (message "lang =3D %s" lang)
+    (cond
+     ((eq lang 'html) (html-ts-mode--defun-name node))
+     ((eq lang 'javascript) (js--treesit-defun-name node))
+     ((eq lang 'css) (css--treesit-defun-name node)))))
+
+(define-derived-mode mhtml-ts-mode html-mode
+  '("HTML+" (:eval (let ((lang (mhtml-ts-mode--language-at-point (point))))
+                     (cond ((eq lang 'html) "")
+                           ((eq lang 'javascript) "JS")
+                           ((eq lang 'css) "CSS")))))
+  "Major mode for editing HTML with embedded JavaScript and CSS.
+Powered by tree-sitter."
+  (if (not (and
+            (treesit-ready-p 'html)
+            (treesit-ready-p 'javascript)
+            (treesit-ready-p 'css)))
+      (error "Tree-sitter parsers for HTML isn't
+    available.  You can install the parsers with M-x
+    `mhtml-ts-mode-install-parsers'")
+
+    ;; When an language is embedded, you should initialize some variable
+    ;; just like it's done in the original mode.
+
+    ;; Comment.
+    ;; indenting settings for js-ts-mode.
+    (c-ts-common-comment-setup)
+    (setq-local comment-multi-line t)
+
+    ;; Font-lock.
+
+    ;; There are two ways to handle embedded code:
+    ;; 1. Use a single parser for all the embedded code in the buffer. In
+    ;; this case, the embedded code blocks are concatenated together and a=
re
+    ;; seen as a single continuous document to the parser.
+    ;; 2. Each embedded code block gets its own parser. Each parser only s=
ees
+    ;; that particular code block.
+
+    ;; If you go with 2 for a language, the local parsers are created and
+    ;; destroyed automatically by Emacs. So don't create a global parser f=
or
+    ;; that embedded language here.
+
+    ;; Create the parsers, only the global ones.
+    ;; jsdoc is a local parser, don't create a parser for it.
+    (treesit-parser-create 'css)
+    (treesit-parser-create 'javascript)
+
+    ;; Multi-language modes must set the  primary parser.
+    (setq-local treesit-primary-parser (treesit-parser-create 'html))
+
+    (setq-local treesit-range-settings
+                (treesit-range-rules
+                 :embed 'javascript
+                 :host 'html
+                 :offset '(1 . -1)
+                 '((script_element
+                    (start_tag (tag_name))
+                    (raw_text) @cap))
+
+                 :embed 'css
+                 :host 'html
+                 :offset '(1 . -1)
+                 '((style_element
+                    (start_tag (tag_name))
+                    (raw_text) @cap))))
+
+    ;; jsdoc is not mandatory for js-ts-mode, so we respect this by
+    ;; adding jsdoc range rules only when jsdoc is available.
+    (when (treesit-ready-p 'jsdoc t)
+      (setq-local treesit-range-settings
+                  (append treesit-range-settings
+                          (treesit-range-rules
+                           :embed 'jsdoc
+                           :host 'javascript
+                           :local t
+                           `(((comment) @cap
+                              (:match ,js--treesit-jsdoc-beginning-regexp =
@cap))))))
+      (setq-local c-ts-common--comment-regexp
+            (rx (or "comment" "line_comment" "block_comment" "description"=
))))
+
+
+    ;; Many treesit fuctions need to know the language at-point.
+    ;; So you should define such a function.
+    (setq-local treesit-language-at-point-function #'mhtml-ts-mode--langua=
ge-at-point)
+
+    ;; Indent.
+
+    ;; Since mhtl-ts-mode inherits indentation rules from html-ts-mode, js
+    ;; and css, if you want to change the offset you have to act on the
+    ;; *-offset variables defined for those languages.
+
+    ;; JavaScript and CSS must be indented relative to their code block.
+    ;; This is done by inserting a special rule before the normal
+    ;; indentation rules of these languages.
+    ;; The value of mhtml-ts-mode--js-css-indent-offset changes based on
+    ;; mhtml-ts-mode-tag-relative-indent and can be used to indent
+    ;; JavaScript and CSS code relative to the HTML that contains them,
+    ;; just like in mhtml-mode.
+    (setq-local treesit-simple-indent-rules
+                (append html-ts-mode--indent-rules
+                        ;; Extended rules for js and css, to
+                        ;; indent appropriately when injected
+                        ;; into html
+                        `((javascript ((parent-is "program")
+                                       mhtml-ts-mode--js-css-tag-bol
+                                       mhtml-ts-mode--js-css-indent-offset)
+                                      ,@(cdr (car js--treesit-indent-rules=
))))
+                        `((css ((parent-is "stylesheet")
+                                mhtml-ts-mode--js-css-tag-bol
+                                mhtml-ts-mode--js-css-indent-offset)
+                               ,@(cdr (car css--treesit-indent-rules))))))
+    ;; Navigation.
+
+    ;; This is for finding defun name, it's used by IMenu as default
+    ;; function no specific functions are defined.
+    (setq-local treesit-defun-name-function #'mhtml-ts-mode--defun-name)
+
+    ;; Define what are 'thing' for treesit.
+    ;; 'Thing' is a symbol representing the thing, like `defun', `sexp', or
+    ;; `sentence'.
+    ;; As an alternative, if you want just defun, you can define a `treesi=
t-defun-type-regexp'.
+    (setq-local treesit-thing-settings
+                `((html
+                   (defun "element")
+                   (sexp ,(regexp-opt '("element"
+                                        "text"
+                                        "attribute"
+                                        "value")))
+                   (sentence "tag")
+                   (text ,(regexp-opt '("comment" "text"))))
+                  (javascript
+                   (defun ,(rx (or "class_declaration"
+                                   "method_definition"
+                                   "function_declaration"
+                                   "lexical_declaration")))
+                   (sexp ,(regexp-opt js--treesit-sexp-nodes 'symbols))
+                   (sentence ,(regexp-opt js--treesit-sentence-nodes 'symb=
ols))
+                   (text ,(regexp-opt
+                           '("comment"
+                             "string_fragment")
+                           'symbols)))
+                  (css
+                   (defun "rule_set"))))
+
+    ;; Font-lock.
+
+    ;; In a multi-language scenario, font lock settings are usually a
+    ;; concatenation of language rules. As you can see, it is possible
+    ;; to extend/modify the default rule or use a different set of
+    ;; rules. See `php-ts-mode--custom-html-font-lock-settings' for more
+    ;; advanced usage.
+    (setq-local treesit-font-lock-settings
+                (append html-ts-mode--font-lock-settings
+                        js--treesit-font-lock-settings
+                        (append
+                         ;; Rule for coloring CSS property values.
+                         ;; Placed before `css--treesit-settings'
+                         ;; to win against the same rule contained therein.
+                         (treesit-font-lock-rules
+                          :language 'css
+                          :override t
+                          :feature 'variable
+                          '((plain_value) @mhtml-ts-mode--colorize-css-val=
ue))
+                         css--treesit-settings)))
+
+    ;; Tells treesit the list of features to fontify.
+    (setq-local treesit-font-lock-feature-list mhtml-ts-mode--feature-list)
+
+    ;; Imenu
+
+    ;; Setup Imenu: if no function is specified, try to find an object
+    ;; using `treesit-defun-name-function'.
+    ;; TODO: we need to see if it is possible to extend Imenu to
+    ;; embedded languages =E2=80=8B=E2=80=8Bas well.
+    (setq-local treesit-simple-imenu-settings
+                `(("Element" "\\`tag_name\\'" nil nil)))
+
+    (treesit-major-mode-setup)))
+
+(when (and (treesit-ready-p 'html) (treesit-ready-p 'javascript) (treesit-=
ready-p 'css))
+  (add-to-list
+   'auto-mode-alist '("\\.[sx]?html?\\(\\.[a-zA-Z_]+\\)?\\'" . mhtml-ts-mo=
de)))
+
+(provide 'mhtml-ts-mode)
+;;; mhtml-ts-mode.el ends here
=2D-=20
2.47.1


--nextPart2282944.vFx2qVVIhK--







Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 11 Dec 2024 04:55:30 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Dec 10 23:55:30 2024
Received: from localhost ([127.0.0.1]:60731 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tLEl0-0001rZ-Es
	for submit <at> debbugs.gnu.org; Tue, 10 Dec 2024 23:55:30 -0500
Received: from mail-pf1-f170.google.com ([209.85.210.170]:58612)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <casouri@HIDDEN>) id 1tLEky-0001rM-Eq
 for 74610 <at> debbugs.gnu.org; Tue, 10 Dec 2024 23:55:29 -0500
Received: by mail-pf1-f170.google.com with SMTP id
 d2e1a72fcca58-728ec840a8aso614038b3a.0
 for <74610 <at> debbugs.gnu.org>; Tue, 10 Dec 2024 20:55:28 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1733892863; x=1734497663; darn=debbugs.gnu.org;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:from:to:cc:subject:date
 :message-id:reply-to;
 bh=dD94BsvE2dF9CRdO0SWIBE/SakRHzvt7rHJOn2ir08c=;
 b=WhJj2pcaldyH5XuSO2J2cP+GYtycmyPAhT7LGUP63w89R6FNYZSvBGggpivTJ4ChP3
 b7P10jOQ9Kh6kpZ+NXyd8iOIWU7cqRtT3OKne1NhGzLDTdNYch5ggwyf5Yxqifgvqpo2
 6xOobqULPL62ehu+Rtliz9deIq+OI5KP5Q3KAVsrKR50UjI1qZeaz3UO8hNkFW6E/uja
 YLeZ9A1dOqHkBVGJxgyGyR6xIfIc1lP5LKxQ1bLhrVSipza02mtDXTiJXtBR/eplqOe8
 QmO7Bp6OjoqOECs1CCs4eU4WUcWkoXccyGjFNgI+/7Q6sD85NB8GnpRxSA2XGxxAS5zd
 k7/Q==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1733892863; x=1734497663;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=dD94BsvE2dF9CRdO0SWIBE/SakRHzvt7rHJOn2ir08c=;
 b=hzwPN+NG3LWt72pBZtPqywioAIA/JRfCHqyM5bK6Lk+z2Rl3dTMnZbI9D4iYWfxd+E
 Dq8yboeYEDAa2vUSVxeRPXRS2iQj6/FfiEx+ghmgc2WzXsXJ4TbN8th9wcb7WAQqQy5g
 o5jRWF/fFYj0oiFcfqsWtLTrZ36R9dhsFO/466UjbJbGz3lxZLc1ZVHfwkes5aJFg0VV
 lkdGkRvvBaXrIHcqGhC8yPDAvDEKBXeEnUMVeMbCUMqr4JSY1gOyyFaszdC9xqFozWOQ
 L2nr67p+ts/BibsAbMilk5RSeCd1wNm048Z16IEW51ILVcjaCnID+Tm44oW8UZYJ+1lz
 XtKg==
X-Gm-Message-State: AOJu0YwtZMBpoj//kOupXXi21Td7LkurII33w+welQWAGNsX9/Q5CKLu
 9imRUteyAj3JkMhcoWsesueOuZUHruWZCzKwuHmSaItgLDaClUjt1OGC3w==
X-Gm-Gg: ASbGncu15KT65Cn2OGOjeAlkuzYywvPvsibqFmcmhT4WxLmD90xybPfoQEek2Q0j9eW
 BeZx3JSGSGND5WUH8Xe4RVssRp4ALM327Mws8PNDf4Jf+HJh/NrkvYJte/D2lWdRbnWi3FFFaE7
 fKMFLQxWCB+IduP5CiWKhPoO5/yJcNvpcfdf9vWdEN+Zl461FUgMWHlz2HwsHKTRaMj4fZoADyD
 Se+ZZ5Sr64qky/jo3JvEnE+IYNOaRTDBoateDDaLAz+Q9yD/LaJ6eKtwMXGBUhSPOlDVHOS2b/f
 WA==
X-Google-Smtp-Source: AGHT+IE4n7ewo3hRqGz8ww7CnHzuxP2jLHhVgesJD82EjFPsSVJ7BDrpctUjqNRujFM6v2Axi/TSEQ==
X-Received: by 2002:a05:6a00:148c:b0:725:e309:7110 with SMTP id
 d2e1a72fcca58-728ed3b31f8mr2659987b3a.5.1733892862791; 
 Tue, 10 Dec 2024 20:54:22 -0800 (PST)
Received: from smtpclient.apple ([2601:646:8f81:6120:f90e:3b71:6ee2:6197])
 by smtp.gmail.com with ESMTPSA id
 d2e1a72fcca58-725c6f6d81dsm7938234b3a.24.2024.12.10.20.54.21
 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);
 Tue, 10 Dec 2024 20:54:22 -0800 (PST)
Content-Type: text/plain;
	charset=utf-8
Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3776.700.51\))
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
From: Yuan Fu <casouri@HIDDEN>
In-Reply-To: <2551656.XAFRqVoOGU@HIDDEN>
Date: Tue, 10 Dec 2024 20:54:09 -0800
Content-Transfer-Encoding: quoted-printable
Message-Id: <0083FFEF-128F-4C5C-B6BC-92CF657416C4@HIDDEN>
References: <3532547.LZWGnKmheA@fedora>
 <7067C127-FE4E-4E1A-8FFC-2BAB221E5B45@HIDDEN>
 <2551656.XAFRqVoOGU@HIDDEN>
To: Vincenzo Pupillo <v.pupillo@HIDDEN>
X-Mailer: Apple Mail (2.3776.700.51)
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)



> On Dec 3, 2024, at 6:29=E2=80=AFAM, Vincenzo Pupillo =
<v.pupillo@HIDDEN> wrote:
>=20
> In data domenica 1 dicembre 2024 07:01:21 Ora standard dell=E2=80=99Euro=
pa centrale,=20
> Yuan Fu ha scritto:
>> It's not uncommon to see different indent offset for CSS and
>> Javascript, so it's a good idea to have separate control for them.
>=20
> Is the behavior the same as mhtml-mode, or would you like something =
like this?
>=20
>    <style>
>                                  z {
>                                      color: red;
>                                  }
>    </style>
>    <script>
>        function myFunction(p1, p2) {
>            return p1 * p2;
>        }
>    </script>
>=20
> The mhtml-ts-mode-js-css-indent-offset variable controls only the =
indentation=20
> relative to the <style> and <script> tags.

Ah, I see, it=E2=80=99s the offset from the enclosing tag. In that case =
it should be fine to use a common variable.

Yuan=




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 6 Dec 2024 13:41:01 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Fri Dec 06 08:41:01 2024
Received: from localhost ([127.0.0.1]:42378 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tJYZp-0001Q7-4l
	for submit <at> debbugs.gnu.org; Fri, 06 Dec 2024 08:41:01 -0500
Received: from mail-wm1-f43.google.com ([209.85.128.43]:46322)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <v.pupillo@HIDDEN>) id 1tJYZn-0001Pz-43
 for 74610 <at> debbugs.gnu.org; Fri, 06 Dec 2024 08:40:59 -0500
Received: by mail-wm1-f43.google.com with SMTP id
 5b1f17b1804b1-434a0fd9778so20683185e9.0
 for <74610 <at> debbugs.gnu.org>; Fri, 06 Dec 2024 05:40:59 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1733492398; x=1734097198; darn=debbugs.gnu.org;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:to:from:from:to:cc:subject:date:message-id
 :reply-to; bh=m23r46S72E2DVABQY+lFe6InucdYHDxQ5oFVNfOaWD4=;
 b=lRTBb9ctFRK45j+wEVsWP2OAZz1mnyNgLaruCCJk+a+Low7vHIGqreU6CmRo20kTa+
 s9Gj4s+JYQvOMfKypqdJleUKnUfXo696/OFGRgBzUBfcxvUT4qIgJcs/flhzgp6XPRF8
 oSq2/DkxyyIkEUjNx6y6+FFhLLUxdzL6ThX3WB04FZ0EBxrhtLZxkGh1l2uKz9fWEwEY
 QHSrkr5BT1dUD/UZoeNddb7NJtmKsopfW7O9jIZa74cNJN5U0K7ZODwD2iDTk7G6OaaE
 YPrbiz8bbE671Dst/kfbU25TCqV717bf8VKVzqqUD7S2wvRciy+1ejvCDr6ApVJeXoqx
 9jBA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1733492398; x=1734097198;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=m23r46S72E2DVABQY+lFe6InucdYHDxQ5oFVNfOaWD4=;
 b=jBoWprEkPsFLhLWk7+Vdg8wwZjboXlBYJeuD/RwKr+xq3CMG29ZNwZFjneptmHiGAJ
 Xfx83+DmDkSBx0G3XOG6yMwWAv+tbdSKtwrp9VeFKwr8dFqbSgIxqn5WXt+sE8jexsmX
 wxRvxWbJf3CtMAP1Le1x3rA5N90uA02oBHmwmG7pnKUrk5s1NbP8m4txH9HZERRMhbwl
 5kB5IrOjkLRuVA05tM5OcPJzxGmdyhDpY7HX+xUqUEeT6gROmfrj8awpsO0OO1MOqo2P
 HBROXPkIV6Xb+sRsRJNDLW8TFpKH7XrBKQdQJYlv5iDuILCk8kJJObuPY0NbaI1zKth3
 1Vvg==
X-Gm-Message-State: AOJu0YzloRkb5WJkLnYSSbt+xOtg9WukhMKtQI2BkDwB6V+lfcYYiLfq
 omekdiIgyvqZp42seXJOI+uDLFXemYK9TcTFy8prYhQuTjrJqAPKe4EEWQ==
X-Gm-Gg: ASbGncugjyVbefRdH1yMX3kJnDa3N4B/l6uW2c4YmtaEhOVulr9CbEeu0ygRtt1J2Jl
 0YnZhSoxgRWUdJ1BKqS7yTSPFfCTRQizyWAQxXCykcp62WfepMnFtJJXIjShDm1vqPcwkomeFTV
 FEPMbMqelxGbm0Jrcu/QAEiy6tzx3ilplKS6rc/8uuu8Rna7ae1nuGwVjZIaIG3SUEUR6431uuV
 19oqaUEmY1CfP+HeaFeBKom1MfPHIHqosXl6sJv6m/TVzpIhFHTAKAb9J3+hp5TiLQnVJozFi6Y
 h8Y=
X-Google-Smtp-Source: AGHT+IHK7M3MtbKIGSAXHOACUb8NUjKWtiIkc6v1ko7450bY17Qpg8tyVe5PiT+DVALO9a/nQutWgQ==
X-Received: by 2002:a5d:6c6a:0:b0:385:e176:4420 with SMTP id
 ffacd0b85a97d-3862b33d313mr2374765f8f.10.1733492398091; 
 Fri, 06 Dec 2024 05:39:58 -0800 (PST)
Received: from 3-191.divsi.unimi.it (3-191.divsi.unimi.it. [159.149.3.191])
 by smtp.gmail.com with ESMTPSA id
 ffacd0b85a97d-3862229a88asm4524304f8f.106.2024.12.06.05.39.57
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Fri, 06 Dec 2024 05:39:57 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: 74610 <at> debbugs.gnu.org, Dmitry Gutov <dmitry@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Fri, 06 Dec 2024 14:39:56 +0100
Message-ID: <2157014.9o76ZdvQCi@HIDDEN>
In-Reply-To: <97c6e669-b9a2-4e1c-b224-227bb65de0fd@HIDDEN>
References: <3532547.LZWGnKmheA@fedora>
 <22104588.4csPzL39Zc@HIDDEN>
 <97c6e669-b9a2-4e1c-b224-227bb65de0fd@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Ciao Dmitry,

In data gioved=C3=AC 5 dicembre 2024 17:51:55 Ora standard dell=E2=80=99Eur=
opa centrale,=20
Dmitry Gutov ha scritto:
> Hi Vincenzo,
>=20
> On 04/12/2024 12:47, Vincenzo Pupillo wrote:
> >> Do you foresee cases for when html-ts-mode would be preferred by the
> >> user instead of this advanced mhtml-ts-mode?
> >=20
> > For everyday use mhtml-ts-mode is better, just like mhtml-mode (which h=
as
> > been the default for html editing for a while now).
> >=20
> >> Or maybe the former is
> >> better in its current shape when used by e.g. php-ts-mode?
> >=20
> > Yes, personally I think that major modes that handle (for tree-sitters)
> > only one language are easier to put together at the moment. It's Lego v=
s.
> > Playmobil.
> > We are in an experimental phase, like all other editors.
> > See https://github.com/helix-editor/helix/pull/1170#issuecomment-997294=
090
> > In some ways, by having a different approach from other editors, we hav=
e a
> > greater degree of flexibility IMHO.
>=20
> Makes sense, thanks.
>=20
> >> In other words, I'm wondering why not update the existing mode with
> >> sub-parsers rather than add a new one. html-mode had such a reason -
> >> it's quite old, and has been used in various placed the way it is now
> >> (including multi-mode packages). But ts modes don't work too well with
> >> multi-mode packages, not currently anyway.
> >=20
> > It's something I've thought about but haven't tried yet.
> > One of the themes of the email thread (on emacs-devel) was to have a
> > simple
> > multi language major mode that was also a sort of =E2=80=9Cuser's guide=
=2E=E2=80=9D
>=20
> I though the updated html-ts-mode could be that mode. Anyway, good to
> hear that this alternative had been given consideration.

Since the development of Emacs 31 has just started and this major mode is n=
ot=20
urgent, I will try some experiments in the next few days to see if html-ts-
mode can be modified without compromising the integration with php-ts-mode.

Vincenzo






Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 5 Dec 2024 16:52:09 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Thu Dec 05 11:52:09 2024
Received: from localhost ([127.0.0.1]:40470 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tJF5F-0007G3-Bx
	for submit <at> debbugs.gnu.org; Thu, 05 Dec 2024 11:52:09 -0500
Received: from fhigh-a2-smtp.messagingengine.com ([103.168.172.153]:50561)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <dmitry@HIDDEN>) id 1tJF5B-0007FP-P6
 for 74610 <at> debbugs.gnu.org; Thu, 05 Dec 2024 11:52:08 -0500
Received: from phl-compute-09.internal (phl-compute-09.phl.internal
 [10.202.2.49])
 by mailfhigh.phl.internal (Postfix) with ESMTP id A1A2E1140151;
 Thu,  5 Dec 2024 11:51:59 -0500 (EST)
Received: from phl-mailfrontend-02 ([10.202.2.163])
 by phl-compute-09.internal (MEProxy); Thu, 05 Dec 2024 11:51:59 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gutov.dev; h=cc
 :content-transfer-encoding:content-type:content-type:date:date
 :from:from:in-reply-to:in-reply-to:message-id:mime-version
 :references:reply-to:subject:subject:to:to; s=fm3; t=1733417519;
 x=1733503919; bh=6teG8DK9ot3T1ruqcwtAG77vCP+PYhnVREjdnM5QF98=; b=
 IiajMP3JpV/3knqceeD3dm6qjFjb31jMH20/OgR2lZx454O97Y66QCMQw1hR4e4k
 di17lt9/5fYF+yXofS8sX08n3PcN4VVsBSHCrtUVBWhkumBiX4WioPjeXC1swyT1
 qDVkKdC41f3qQFVUDFCUUGrOnOiGP1LXvlD2QbwKXn+oFVQnUY85pNsOc70sxdin
 x5u7Y1FHAn3OVhZHxKYIGgt1rzSv4DaVQPw3F6PmExqpErgEbg7GpUBeXnQUhsXG
 gf9U9AtStcwwrM1hnxbav/85iojihjZaFWd292Se1/PasirTxBNqZFOm/WA3MJkl
 IOY8FhtWquPZb7+FHuqPMQ==
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=
 messagingengine.com; h=cc:content-transfer-encoding:content-type
 :content-type:date:date:feedback-id:feedback-id:from:from
 :in-reply-to:in-reply-to:message-id:mime-version:references
 :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender
 :x-me-sender:x-sasl-enc; s=fm1; t=1733417519; x=1733503919; bh=6
 teG8DK9ot3T1ruqcwtAG77vCP+PYhnVREjdnM5QF98=; b=kW0wi+uNaqEDzXIJr
 xbP48JREDNM/iMUA+MuJpW2nPhSeouNfYHabI+jCqlQdo18Ba8zdMN+TiyE0Rl8V
 GG/nfsa+V29CYHU7+SkCM3iVhDonHyyc0xCtReoPmVkacUeMoYyZP61m1HUoj7/R
 SnMFKs43tAQulOKU5PqBvq0GycREmbepsV4FMqwk9r28xhCuu2bkmzoCd9ZL1Rs9
 xNJWwWe7a5K4XjNaoZwYR9UykFEgTPoxSID6k5JoESBqPGdbMzVLn7Az1w56JBuG
 ejx2nZl6chOqMdo1WGBuXb6loerOplcjXGSsvrEy1K9AhuVa1q6sy2Ekcbn5KzxN
 0a5GA==
X-ME-Sender: <xms:L9pRZ7M240HZyWGxdNwZKWOqQiFp3dzGGSBo0GV-OhKo0xh4k8bfAg>
 <xme:L9pRZ1843R8OWqRDerBz9q2vu7V66hR4iOTcF1Ap3uk22RuQVylA2J6w-tE1l-966
 rxIH_UoMBIotHEB5hI>
X-ME-Received: <xmr:L9pRZ6SyyqLTtsjhdhgdvtMfnNCELRwmUkjOpgxa-xRPy_tyg2_LitbaeDrhUmeMcfrQ>
X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefuddrieejgdelvdcutefuodetggdotefrodftvf
 curfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdpuffr
 tefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnth
 hsucdlqddutddtmdenucfjughrpefkffggfgfuvfhfhfgjtgfgsehtkeertddtvdejnecu
 hfhrohhmpeffmhhithhrhicuifhuthhovhcuoegumhhithhrhiesghhuthhovhdruggvvh
 eqnecuggftrfgrthhtvghrnhepheekfedtveetkeehheffkedtffevfeffieellefhgfeg
 uefhtdekfeeuveeiudfgnecuffhomhgrihhnpehgihhthhhusgdrtghomhenucevlhhush
 htvghrufhiiigvpedtnecurfgrrhgrmhepmhgrihhlfhhrohhmpegumhhithhrhiesghhu
 thhovhdruggvvhdpnhgspghrtghpthhtohepvddpmhhouggvpehsmhhtphhouhhtpdhrtg
 hpthhtohepvhdrphhuphhilhhlohesghhmrghilhdrtghomhdprhgtphhtthhopeejgeei
 uddtseguvggssghughhsrdhgnhhurdhorhhg
X-ME-Proxy: <xmx:L9pRZ_vT4Jgx-ZJKqs456MaSJVSlAS1pK3eSFbqntzfyxvltZdkkGA>
 <xmx:L9pRZzeI1WdmkOogLrMPSEnduG_ZPBQRn2dJy_gAqtZ9_Z9EdeIo7w>
 <xmx:L9pRZ70F59N_njNEzpP_oDk--Fn87T7gYc-Ki0789utI0qoRRDVOpA>
 <xmx:L9pRZ__QXmtMPk9pmulrsOu7CX_H3kKNUUbXz2hdpvSv0IjVXRb3Jw>
 <xmx:L9pRZ0rF3peTyfT-EKf3Ve3fpC8L370flRyU8q3rjxFl1FmEE5oc96h7>
Feedback-ID: i07de48aa:Fastmail
Received: by mail.messagingengine.com (Postfix) with ESMTPA; Thu,
 5 Dec 2024 11:51:58 -0500 (EST)
Message-ID: <97c6e669-b9a2-4e1c-b224-227bb65de0fd@HIDDEN>
Date: Thu, 5 Dec 2024 18:51:55 +0200
MIME-Version: 1.0
User-Agent: Mozilla Thunderbird
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
To: Vincenzo Pupillo <v.pupillo@HIDDEN>, 74610 <at> debbugs.gnu.org
References: <3532547.LZWGnKmheA@fedora>
 <b1edc2ed-3717-4e3a-a043-10d6f78f8f53@HIDDEN>
 <22104588.4csPzL39Zc@HIDDEN>
Content-Language: en-US
From: Dmitry Gutov <dmitry@HIDDEN>
In-Reply-To: <22104588.4csPzL39Zc@HIDDEN>
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi Vincenzo,

On 04/12/2024 12:47, Vincenzo Pupillo wrote:

>> Do you foresee cases for when html-ts-mode would be preferred by the
>> user instead of this advanced mhtml-ts-mode?
> For everyday use mhtml-ts-mode is better, just like mhtml-mode (which has been
> the default for html editing for a while now).
> 
>> Or maybe the former is
>> better in its current shape when used by e.g. php-ts-mode?
> Yes, personally I think that major modes that handle (for tree-sitters) only
> one language are easier to put together at the moment. It's Lego vs.
> Playmobil.
> We are in an experimental phase, like all other editors.
> See https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090
> In some ways, by having a different approach from other editors, we have a
> greater degree of flexibility IMHO.

Makes sense, thanks.

>> In other words, I'm wondering why not update the existing mode with
>> sub-parsers rather than add a new one. html-mode had such a reason -
>> it's quite old, and has been used in various placed the way it is now
>> (including multi-mode packages). But ts modes don't work too well with
>> multi-mode packages, not currently anyway.
> 
> It's something I've thought about but haven't tried yet.
> One of the themes of the email thread (on emacs-devel) was to have a simple
> multi language major mode that was also a sort of “user's guide.”

I though the updated html-ts-mode could be that mode. Anyway, good to 
hear that this alternative had been given consideration.




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 4 Dec 2024 10:49:01 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Dec 04 05:49:01 2024
Received: from localhost ([127.0.0.1]:34420 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tImwH-0001w8-BB
	for submit <at> debbugs.gnu.org; Wed, 04 Dec 2024 05:49:01 -0500
Received: from mail-wm1-f47.google.com ([209.85.128.47]:52441)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <v.pupillo@HIDDEN>) id 1tImwE-0001vi-II
 for 74610 <at> debbugs.gnu.org; Wed, 04 Dec 2024 05:49:00 -0500
Received: by mail-wm1-f47.google.com with SMTP id
 5b1f17b1804b1-434aa222d96so80784995e9.0
 for <74610 <at> debbugs.gnu.org>; Wed, 04 Dec 2024 02:48:58 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1733309273; x=1733914073; darn=debbugs.gnu.org;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:to:from:from:to:cc:subject:date:message-id
 :reply-to; bh=VPbGiEPlpqleBoGEBtOgHWufnaWPJLRRYumn8cELD6g=;
 b=F/bCm1QpGBLBD+wjX2k4n6TFfCZ0RVe2FjhBxQNyAwVo5iTJcJpo3381yBhTucon3s
 14tvksg6bnPuUBd9cpHF+RXmOQ76PoGbL7i+xGvU5DTGJateCHa4CvsstfyeuRvJwC1k
 PNRYNL89Tpnh/C61PQOHUqiTt1dlohaYNwsYnd62BYWM0RMkd9XCcx6NdPntkmtBoSHA
 bSjggTdkShkcRbICEn3gP0rNrewnLSPDldN/i91LwnT5KS7NZbyeR/CxhvqcSIq8Cgtc
 65VqiHSd5h3SR61eIUgSNLrWMK+XfzG6U/A1hbZQM5AVyYpanw3Yze2PHLYojamGkIYA
 hcZg==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1733309273; x=1733914073;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=VPbGiEPlpqleBoGEBtOgHWufnaWPJLRRYumn8cELD6g=;
 b=logYhRDqF5HJ5Na5Tx1xVb45L9QgmCLahs2QHj82EwdiGqWGUezPnVeaO1AXxXJ+50
 Im5xBJmnj0U2dmnXju0ck2GaqQGMQ/xctRwR+S9aCvj8Q/vrWFqAe7ps0Kqd1dxvTSed
 nbSf1pRDIWGdLMme+6M5apKX8Pq68+4A866tEZlL5XJ7A22duIQ5HhfDwsR5ilh+amxQ
 6Oec1heY+67pWQaAnJLi/GNPA69GaP5Xx5TqBP041Du2hBuJxJ0TPOJGR4ldJjESB2oW
 NXP1aGfVhwVOVwK+Z7SNJPKydw2ztk8RVoOWP/CRQwaAKQwGwcGzXNbk1//jOG29BhYD
 IORw==
X-Forwarded-Encrypted: i=1;
 AJvYcCWbB6N8rBvAzfLScsZLkneF0j1q/t2qLVLl0OZYphOGG3DE5U+UTyMDzg9uUfw7/OOziTQSfA==@debbugs.gnu.org
X-Gm-Message-State: AOJu0Yyp4yTvvuz6bVJE60Zmh4np5kvcILVctabzEr8+g4cfXSGINqCG
 oGh61wZMShxjS5SnTjqZJuo1T55lhUrXl384aJx2+moddzycW235
X-Gm-Gg: ASbGnct9EMWp7yPw3ADd3ghHGWGO5mbaCy84prQpXf1eg2agNNhYM7lK1VSDwKUQiSz
 ZHEQ2wKztm6uHuN7g0eIbRJZriyD4yzmRXsja8bsKTUFg5X1eaRKoz0WKnezK7TS79NHc9/3xwH
 9nSHQx9E6DzdT7lApj5l200l2NDKmAGb34RlFyNsFU7sHFkCaLFaozyk4c2RamhwAuQVcP0gofU
 +ma0vlEvKbtzIWR1okfLPgTN92qEZDXhOeijW1mUwG4lt4g1UJoZD2w7sA+8cS2qJpPBI7ZEr/E
 ffM=
X-Google-Smtp-Source: AGHT+IHx/vg3CFryNeBqnVBFjGu4NPySh87nW5vT21hvUupoPeYr5LshMQVS5en0gmfeqv7uA4nb6Q==
X-Received: by 2002:a05:600c:3546:b0:434:9fac:b157 with SMTP id
 5b1f17b1804b1-434d09c10a5mr59455105e9.13.1733309272394; 
 Wed, 04 Dec 2024 02:47:52 -0800 (PST)
Received: from 3-191.divsi.unimi.it (3-191.divsi.unimi.it. [159.149.3.191])
 by smtp.gmail.com with ESMTPSA id
 5b1f17b1804b1-434d52b77f4sm19939465e9.43.2024.12.04.02.47.51
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Wed, 04 Dec 2024 02:47:52 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: Dmitry Gutov <dmitry@HIDDEN>, 74610 <at> debbugs.gnu.org
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Wed, 04 Dec 2024 11:47:51 +0100
Message-ID: <22104588.4csPzL39Zc@HIDDEN>
In-Reply-To: <b1edc2ed-3717-4e3a-a043-10d6f78f8f53@HIDDEN>
References: <3532547.LZWGnKmheA@fedora>
 <b1edc2ed-3717-4e3a-a043-10d6f78f8f53@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi Dmitry,

In data mercoled=C3=AC 4 dicembre 2024 02:27:53 Ora standard dell=E2=80=99E=
uropa centrale,=20
hai scritto:
> On 29/11/2024 23:57, Vincenzo Pupillo wrote:
> > +;; This package provides `mhtml-ts-mode' which is a major mode
> > +;; for editing HTML files with embedded JavaScript and CSS.
> > +;; Tree Sitter is used to parse each of these languages.
> > +;;
> > +;; Please note that this package requires `html-ts-mode', which
> > +;; registers itself as the major mode for editing HTML.
>=20
> Hi!
>=20
> Do you foresee cases for when html-ts-mode would be preferred by the
> user instead of this advanced mhtml-ts-mode?=20
=46or everyday use mhtml-ts-mode is better, just like mhtml-mode (which has=
 been=20
the default for html editing for a while now).

> Or maybe the former is
> better in its current shape when used by e.g. php-ts-mode?
Yes, personally I think that major modes that handle (for tree-sitters) onl=
y=20
one language are easier to put together at the moment. It's Lego vs.=20
Playmobil.
We are in an experimental phase, like all other editors.
See https://github.com/helix-editor/helix/pull/1170#issuecomment-997294090
In some ways, by having a different approach from other editors, we have a=
=20
greater degree of flexibility IMHO.

>=20
> In other words, I'm wondering why not update the existing mode with
> sub-parsers rather than add a new one. html-mode had such a reason -
> it's quite old, and has been used in various placed the way it is now
> (including multi-mode packages). But ts modes don't work too well with
> multi-mode packages, not currently anyway.

It's something I've thought about but haven't tried yet.=20
One of the themes of the email thread (on emacs-devel) was to have a simple=
=20
multi language major mode that was also a sort of =E2=80=9Cuser's guide.=E2=
=80=9D


Vincenzo.






Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 4 Dec 2024 01:28:08 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Dec 03 20:28:08 2024
Received: from localhost ([127.0.0.1]:33488 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tIeBT-00084O-Nc
	for submit <at> debbugs.gnu.org; Tue, 03 Dec 2024 20:28:08 -0500
Received: from fhigh-b5-smtp.messagingengine.com ([202.12.124.156]:54187)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <dmitry@HIDDEN>) id 1tIeBR-00083q-Ab
 for 74610 <at> debbugs.gnu.org; Tue, 03 Dec 2024 20:28:06 -0500
Received: from phl-compute-01.internal (phl-compute-01.phl.internal
 [10.202.2.41])
 by mailfhigh.stl.internal (Postfix) with ESMTP id 46EB32540181;
 Tue,  3 Dec 2024 20:27:57 -0500 (EST)
Received: from phl-mailfrontend-02 ([10.202.2.163])
 by phl-compute-01.internal (MEProxy); Tue, 03 Dec 2024 20:27:57 -0500
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gutov.dev; h=cc
 :content-transfer-encoding:content-type:content-type:date:date
 :from:from:in-reply-to:in-reply-to:message-id:mime-version
 :references:reply-to:subject:subject:to:to; s=fm3; t=1733275677;
 x=1733362077; bh=F7ElMP7JgmzBEX/KNO1ZED78gYXAuTgA7RmzW0N9kVI=; b=
 jo3y+t3AOG4S37alFIhxmg0kiofIMRAWi10RW3/j5G9JVKK3uztv+I+UqCVEx90t
 CNoyRcMRmA7W5aHKJHegwVJEik5J/AEPv6AlENbGsQyBRhzOKotxvww2F0dB8HNB
 NJrPGMFR/unFK66/Wq99YnDEKn3npvvr4cYntGN1QbKGHt9VSOzVG0Tn1sJXBPJo
 xSF9Ks/6qQUmAyNmJCKgVqnnZhbbznZK4PmxiD/BdOXWURoDuvRQobitQoX9Ymym
 1kMuGbiuCe6hmr3vgkO64TLKePzInl2FWi4tT5/F4qCbMhK8UTIQZgFZRdeKEI8S
 Pb/Gyc3sZD3UxIKWHj5F9g==
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=
 messagingengine.com; h=cc:content-transfer-encoding:content-type
 :content-type:date:date:feedback-id:feedback-id:from:from
 :in-reply-to:in-reply-to:message-id:mime-version:references
 :reply-to:subject:subject:to:to:x-me-proxy:x-me-sender
 :x-me-sender:x-sasl-enc; s=fm1; t=1733275677; x=1733362077; bh=F
 7ElMP7JgmzBEX/KNO1ZED78gYXAuTgA7RmzW0N9kVI=; b=BLBT5yyG4Dj+3qxFQ
 8TztqnKxMgLroj2vd27w9wlyuQkzy90Hm+I1AUAPG9IUawSTyumOF3o69IU6ZL7h
 S3Az8s8LDo5R7o1Ptg8Yqvr7hSCh8vRdUb1xkrG7WPcSslcX72sfKVBLoHIQBUjA
 A6xj9SFO4URSt3uhCadYhNfLr9aUei66sa6JbtmtZcdxkybBNhWB7EfTT+4MEFIn
 viRmR8DzxQJOxffERUyFp+nH6ZUN7GzrzQ4c+fPvbTpfiomiT/b2kviAP5eOHn+T
 jxuEO95slr+Y29Fijy+lhDRzQ1jFoMQLNeUut5fjP4OHBcBWFuJc8N0nYtrrWvhP
 mHUNA==
X-ME-Sender: <xms:HLBPZ-0H8hNxgM8IEo_eyhNvmh5T2hU9db5jNPaCu5Dm6Skvria5Tg>
 <xme:HLBPZxFqng3YxP35pe2C-2v0IntbkdO_JEVTrb58cNjSk73vNyUuEZgUUbcCzhiYR
 MUTta00O1kk6fpz64I>
X-ME-Received: <xmr:HLBPZ27Hypy8ZZE1Xau9lKrSnJbkGsyGA9W9dl5Lb-ND44KJGNP_dTwm8YxalwaAMJoi>
X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeefuddrieeggdefvdcutefuodetggdotefrodftvf
 curfhrohhfihhlvgemucfhrghsthforghilhdpggftfghnshhusghstghrihgsvgdpuffr
 tefokffrpgfnqfghnecuuegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnth
 hsucdlqddutddtmdenucfjughrpefkffggfgfuvfhfhfgjtgfgsehtjeertddtvdejnecu
 hfhrohhmpeffmhhithhrhicuifhuthhovhcuoegumhhithhrhiesghhuthhovhdruggvvh
 eqnecuggftrfgrthhtvghrnheptdfhuedvtdevleegueelvedvjeevheffveevhedvueff
 tdefhfdvueeggfetgfdtnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrg
 hilhhfrhhomhepughmihhtrhihsehguhhtohhvrdguvghvpdhnsggprhgtphhtthhopedv
 pdhmohguvgepshhmthhpohhuthdprhgtphhtthhopehvrdhpuhhpihhllhhosehgmhgrih
 hlrdgtohhmpdhrtghpthhtohepjeegiedutdesuggvsggsuhhgshdrghhnuhdrohhrgh
X-ME-Proxy: <xmx:HLBPZ_0pz3Mu1GntjoFfA4YgP3xE2jYY5L_lemKORFDNHyQmbBKdVw>
 <xmx:HLBPZxHzbcaDy55Ly-r9njnlUMOG-83dQfux2CXNQ-A4EOuQkpryvg>
 <xmx:HLBPZ49gxJeGaskFmxDRfbN6X8bD_Gmxzm8wJ9tvL-aOE-QPYOmhlQ>
 <xmx:HLBPZ2koz4VXtmLrGMjpEZgVjlsMeEuTrCUiUcOVOeSgssp75-jSEw>
 <xmx:HbBPZ2TxY0uaj87o96-Zgwtm2CWe_tVpfak6STVWiH9xc_EO_UOG7Tne>
Feedback-ID: i07de48aa:Fastmail
Received: by mail.messagingengine.com (Postfix) with ESMTPA; Tue,
 3 Dec 2024 20:27:55 -0500 (EST)
Message-ID: <b1edc2ed-3717-4e3a-a043-10d6f78f8f53@HIDDEN>
Date: Wed, 4 Dec 2024 03:27:53 +0200
MIME-Version: 1.0
User-Agent: Mozilla Thunderbird
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
To: Vincenzo Pupillo <v.pupillo@HIDDEN>, 74610 <at> debbugs.gnu.org
References: <3532547.LZWGnKmheA@fedora>
Content-Language: en-US
From: Dmitry Gutov <dmitry@HIDDEN>
In-Reply-To: <3532547.LZWGnKmheA@fedora>
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 7bit
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

On 29/11/2024 23:57, Vincenzo Pupillo wrote:
> +;; This package provides `mhtml-ts-mode' which is a major mode
> +;; for editing HTML files with embedded JavaScript and CSS.
> +;; Tree Sitter is used to parse each of these languages.
> +;;
> +;; Please note that this package requires `html-ts-mode', which
> +;; registers itself as the major mode for editing HTML.

Hi!

Do you foresee cases for when html-ts-mode would be preferred by the 
user instead of this advanced mhtml-ts-mode? Or maybe the former is 
better in its current shape when used by e.g. php-ts-mode?

In other words, I'm wondering why not update the existing mode with 
sub-parsers rather than add a new one. html-mode had such a reason - 
it's quite old, and has been used in various placed the way it is now 
(including multi-mode packages). But ts modes don't work too well with 
multi-mode packages, not currently anyway.




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 3 Dec 2024 14:30:16 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Tue Dec 03 09:30:16 2024
Received: from localhost ([127.0.0.1]:58842 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tITup-0001Gm-Ma
	for submit <at> debbugs.gnu.org; Tue, 03 Dec 2024 09:30:15 -0500
Received: from mail-wr1-f41.google.com ([209.85.221.41]:51601)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <v.pupillo@HIDDEN>) id 1tITun-0001Dm-EK
 for 74610 <at> debbugs.gnu.org; Tue, 03 Dec 2024 09:30:14 -0500
Received: by mail-wr1-f41.google.com with SMTP id
 ffacd0b85a97d-385ed7f6605so1505777f8f.3
 for <74610 <at> debbugs.gnu.org>; Tue, 03 Dec 2024 06:30:13 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1733236148; x=1733840948; darn=debbugs.gnu.org;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:from:to:cc:subject:date
 :message-id:reply-to;
 bh=T+IBCQFls2phYwHMLNFhm02JlZanXqjR+rRlSTHGTBc=;
 b=DogUlVWzM73oBKdNiZh/IHBBGbVzyh1iZurGxwvAhjGZZANPHs4NCQ96tgA1bHjBNr
 GM2g8yu2ePncTPRxdg4Ch/K+4eX8tpbAVeyZJxUm2/9FtpB+/XNuRZFyFNwAhb3Cxnvk
 Q+1R6EChnvHTrc0DxIWMFs8L9k9JtO2Hjl7If/qoP0Yn8unL9OBRJua0wB+eQypw9MsQ
 irAZewkTNrsLwM6Mk25Mn5fa6d3Y4/dCFd7d0hlZU1RDINGS1CK4dpFJHqo8AMUBldFE
 ndzKJija9cmxUGxR/eL+o3MK3FyDnXhZR2+Dn4pX+1jC2SQk800yXv2ptj4uO/rvLzMe
 5AMw==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1733236148; x=1733840948;
 h=content-transfer-encoding:mime-version:references:in-reply-to
 :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=T+IBCQFls2phYwHMLNFhm02JlZanXqjR+rRlSTHGTBc=;
 b=GIQWCBUh0d4UL4QY3T6nou4A7/rZ2ZFUsmo9tu3akKXII98WMLpOueelKAG3KeH85a
 SLDT/7mZKxSUZXkh0bHLxMyL/W7WG/4kRR2hTF4ExbD15f2bZ+3nJS5mhRssJty3s5lv
 KPa2BPtc2z8+QwAcQhY8+BkamtEeKtYaT/Wxvn9L1RNst1/JXmHQvt2rE3yfTpEKepzJ
 18pDRJLMvxU1aawKGXIdbGBKAuqWu0HKUWdQCTDvbvNMLMQdTpW0XkasKiqv4lqqpKj9
 ZEDlKcK8AiE1XWlfdTItbY1WiDOP61obEpVad4DW/YZcZl6VUE3tH0ClQwQ0DLMWV8AQ
 6zyw==
X-Gm-Message-State: AOJu0Yz+uE8uV71tI+BFisbFd1OPq1nDwR8/g7RFgrMKWZ2Av+ypW8s2
 ICmT0S8kQC2bhNgFUt6OKgmlit4ypnj62qMqdTFgTe6skxhotBej
X-Gm-Gg: ASbGnctVw5Ivl7BPFwy8/6HfzZU4O5VjXT2pUcA3CiK+w4Kx92ke4dQrJk8v1NzpGn4
 VPT/3IFXbgEx8ycWG60ULfrCMDPlUaf8an9/V7ENSvdz10wXExx+h3BAKPtqlfn8VQjmj5q2KSa
 j4tAcHjiMPcJsKlG4hyLK49YQLdvBr1v23VmEB3l3qY0oC71E3BK4II4fp/0U4fii3y6R3pWSzb
 a5sw3N4eKvKXXStSiOPqZzcWmj2viWrnqSIpMztm+T03FAqwKVaKNub/8Y5gjGPxGLDrEXrAdKc
 Hng=
X-Google-Smtp-Source: AGHT+IF01gSTUYvtfhpsrXavE6BAAO0wp3y7jXSXQrzIr4MM0iwQx+IiZY3B7DfxUIu5yBy6EXLU1w==
X-Received: by 2002:a5d:6487:0:b0:385:edb7:69ba with SMTP id
 ffacd0b85a97d-385fd3ce954mr2282619f8f.1.1733236147529; 
 Tue, 03 Dec 2024 06:29:07 -0800 (PST)
Received: from 3-191.divsi.unimi.it (3-191.divsi.unimi.it. [159.149.3.191])
 by smtp.gmail.com with ESMTPSA id
 5b1f17b1804b1-434aa7e4df1sm222727785e9.39.2024.12.03.06.29.07
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Tue, 03 Dec 2024 06:29:07 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: Yuan Fu <casouri@HIDDEN>
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Tue, 03 Dec 2024 15:29:06 +0100
Message-ID: <2551656.XAFRqVoOGU@HIDDEN>
In-Reply-To: <7067C127-FE4E-4E1A-8FFC-2BAB221E5B45@HIDDEN>
References: <3532547.LZWGnKmheA@fedora>
 <7067C127-FE4E-4E1A-8FFC-2BAB221E5B45@HIDDEN>
MIME-Version: 1.0
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain; charset="utf-8"
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: Eli Zaretskii <eliz@HIDDEN>, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

In data domenica 1 dicembre 2024 07:01:21 Ora standard dell=E2=80=99Europa =
centrale,=20
Yuan Fu ha scritto:
> It's not uncommon to see different indent offset for CSS and
> Javascript, so it's a good idea to have separate control for them.

Is the behavior the same as mhtml-mode, or would you like something like th=
is?

    <style>
                                  z {
                                      color: red;
                                  }
    </style>
    <script>
        function myFunction(p1, p2) {
            return p1 * p2;
        }
    </script>

The mhtml-ts-mode-js-css-indent-offset variable controls only the indentati=
on=20
relative to the <style> and <script> tags.

V.






Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 1 Dec 2024 08:20:15 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Dec 01 03:20:15 2024
Received: from localhost ([127.0.0.1]:50208 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tHfBe-0002Ht-OJ
	for submit <at> debbugs.gnu.org; Sun, 01 Dec 2024 03:20:15 -0500
Received: from mail-pl1-f181.google.com ([209.85.214.181]:47261)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <casouri@HIDDEN>) id 1tHfBc-0002Hc-Jm
 for 74610 <at> debbugs.gnu.org; Sun, 01 Dec 2024 03:20:13 -0500
Received: by mail-pl1-f181.google.com with SMTP id
 d9443c01a7336-21269c8df64so27288205ad.2
 for <74610 <at> debbugs.gnu.org>; Sun, 01 Dec 2024 00:20:12 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1733041152; x=1733645952; darn=debbugs.gnu.org;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:from:to:cc:subject:date
 :message-id:reply-to;
 bh=0ulmYsCD9migbgTXB9/kD5jZovYaFPsxGU2ve0Zj0Rg=;
 b=kATj46FnwYet1r+g2T7/chDG2MKkxj3qYp2KPBOpM/p0uwb81i2eJcT8DW4C5BwLH3
 zLjyQrfvzWd63YqvCHnO7UjOoXZY3YYPtJd26g8uvHQbtOzGIJDQckc9TJgeTQw2wa3A
 Odqb5L7wWTyWKR8+PQpILdndKOdA5AysvWNdwfoRP6UuvxveZ7lxXa96Ga65UDX0Ne1/
 LEeYAas89zFS1TN2daqe7cXhnkr0bHJ50zAjus/9tU4T8yejfzJjnXonWMpoKKqQJHnk
 51ItoAszR24Hn6SIpCkdKlDGLD02UqrGCMqKVTsfniaZsasYawwfO77/rqPcgoxoH3fR
 Itfg==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1733041152; x=1733645952;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=0ulmYsCD9migbgTXB9/kD5jZovYaFPsxGU2ve0Zj0Rg=;
 b=Eq8Pq1Xv+w7xDvBktVR/2tVOFdEmAq6tTueYs6N0898byOOYFzTHXj4Ffk5Uko87n2
 TJqr2kalS3Te7uRpCaY5ozNY8v0dMk9v6PqDT/BgCVKX6XELzkHq6LsI1ep24ZEEATka
 Oh07wPphVr/Ugxt34TXc0gwZLrTVqzcz7DcHbs10MBlZ2czB32zGjV55TfsH/dOf9o/t
 dsypB4E9rdtCRQ5Oyf4ZV3+v5olXHfbXYNEACS+hT2TzYAEaRrV/id7kGC94Qdr8wT1o
 M9I+xiyAMjziAcGPi+sFMfQ72SGQxVq5Z3uG3YIkgz/hHbpkIDkT31Pzt3gZErbUpLYD
 1ohA==
X-Forwarded-Encrypted: i=1;
 AJvYcCXWsoyLDj4fHfESTiIqrnDwEr/OW7nRGhvAAcoVNplmNfBVjcB+lkLHCvVn2gOJoA4EMf7rBg==@debbugs.gnu.org
X-Gm-Message-State: AOJu0YwcPWzF2iVvsK6PnjLCGW9je9Ejz+VsxCVhaEPlYW9eFTpo8KXf
 NQt9YxiLa5JvekfhUlJ7cocjkcQZfDWPkygw8FxUEPMoRQuwH/CW
X-Gm-Gg: ASbGncsu/QB0dyCSaW+nY18KEFkfZS/7iNoa/F4i2Zo1h+laASjqHnesexVV+R5aVWf
 ND82D5EnKee06DNC/q4lLtoP071BRBGDs/she1AB83lhbJMGAuG3iXC9sm2MXHr+klwItgeHVB0
 8kZy2AGfm7/PQHOqFCl3SazKTNfTGK3cSMc5Sx+jpYpLn+7b6uDKhns4m0jxmNpWePWQBp4Z5u1
 5K8knW40e1y/4YB+MgUrtTuSsUb2qkA6779mCHhMuqFrtGlN6VrqlhpZwM0uVU0utavWsAKOQ==
X-Google-Smtp-Source: AGHT+IFp/YBopaF9mNxLV9enVjgVesBNpB8ERorbKrv+XtoC5wXoCE2o+Dwt+67WJOjKGMxjfScHsA==
X-Received: by 2002:a17:903:98f:b0:215:401b:9535 with SMTP id
 d9443c01a7336-215401b96b9mr108517375ad.47.1733041151668; 
 Sun, 01 Dec 2024 00:19:11 -0800 (PST)
Received: from smtpclient.apple ([2601:646:8f81:6120:71b7:718f:7faa:8436])
 by smtp.gmail.com with ESMTPSA id
 d2e1a72fcca58-72541849447sm6440401b3a.181.2024.12.01.00.19.10
 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);
 Sun, 01 Dec 2024 00:19:11 -0800 (PST)
Content-Type: text/plain;
	charset=utf-8
Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3776.700.51\))
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
From: Yuan Fu <casouri@HIDDEN>
In-Reply-To: <86h67n6dfd.fsf@HIDDEN>
Date: Sun, 1 Dec 2024 00:18:59 -0800
Content-Transfer-Encoding: quoted-printable
Message-Id: <C6B64FBC-328D-47FF-B813-02E9C09686E0@HIDDEN>
References: <3532547.LZWGnKmheA@fedora>
 <7067C127-FE4E-4E1A-8FFC-2BAB221E5B45@HIDDEN> <86h67n6dfd.fsf@HIDDEN>
To: Eli Zaretskii <eliz@HIDDEN>
X-Mailer: Apple Mail (2.3776.700.51)
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: v.pupillo@HIDDEN, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)



> On Dec 1, 2024, at 12:00=E2=80=AFAM, Eli Zaretskii <eliz@HIDDEN> =
wrote:
>=20
>> Cc: 74610 <at> debbugs.gnu.org
>> From: Yuan Fu <casouri@HIDDEN>
>> Date: Sat, 30 Nov 2024 22:01:21 -0800
>>=20
>>=20
>>=20
>>> On Nov 29, 2024, at 1:57=E2=80=AFPM, Vincenzo Pupillo =
<v.pupillo@HIDDEN> wrote:
>>>=20
>>> Ciao,
>>> following the discussion=20
>>> https://lists.gnu.org/archive/html/emacs-devel/2024-11/msg00079.html =
I would=20
>>> like to ask if it would be possible to add to emacs this new mode =
for editing=20
>>> html files alternative to mhtml-mode.
>>>=20
>>> Thank you.
>>>=20
>>> Vincenzo.<0001-Add-mhtml-ts-mode.patch>
>>=20
>> Thank you so much! This will be very helpful for others. Here=E2=80=99r=
e some comments.
>=20
> Yuan, I don't see any comments, only what I think is the original
> patch.

Ah, I guess I=E2=80=99m supposed to quote the original patch when making =
comments :-( The lines that aren=E2=80=99t prefixed by + are my =
comments.

Yuan=




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 1 Dec 2024 08:20:14 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Dec 01 03:20:14 2024
Received: from localhost ([127.0.0.1]:50206 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tHfBe-0002Hq-Ci
	for submit <at> debbugs.gnu.org; Sun, 01 Dec 2024 03:20:14 -0500
Received: from mail-pf1-f170.google.com ([209.85.210.170]:47526)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <casouri@HIDDEN>) id 1tHfBc-0002He-QY
 for 74610 <at> debbugs.gnu.org; Sun, 01 Dec 2024 03:20:13 -0500
Received: by mail-pf1-f170.google.com with SMTP id
 d2e1a72fcca58-7256a7a3d98so176415b3a.3
 for <74610 <at> debbugs.gnu.org>; Sun, 01 Dec 2024 00:20:12 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1733041152; x=1733645952; darn=debbugs.gnu.org;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:from:to:cc:subject:date
 :message-id:reply-to;
 bh=0ulmYsCD9migbgTXB9/kD5jZovYaFPsxGU2ve0Zj0Rg=;
 b=kATj46FnwYet1r+g2T7/chDG2MKkxj3qYp2KPBOpM/p0uwb81i2eJcT8DW4C5BwLH3
 zLjyQrfvzWd63YqvCHnO7UjOoXZY3YYPtJd26g8uvHQbtOzGIJDQckc9TJgeTQw2wa3A
 Odqb5L7wWTyWKR8+PQpILdndKOdA5AysvWNdwfoRP6UuvxveZ7lxXa96Ga65UDX0Ne1/
 LEeYAas89zFS1TN2daqe7cXhnkr0bHJ50zAjus/9tU4T8yejfzJjnXonWMpoKKqQJHnk
 51ItoAszR24Hn6SIpCkdKlDGLD02UqrGCMqKVTsfniaZsasYawwfO77/rqPcgoxoH3fR
 Itfg==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1733041152; x=1733645952;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=0ulmYsCD9migbgTXB9/kD5jZovYaFPsxGU2ve0Zj0Rg=;
 b=rDw58xaAGYxwWn75EjuuwO8/AhDNQWvGtUSCa3GWtKS+n5AZOAGcagvUpRwy/h3ZkP
 GTj9v95Wd6pf7GuWWzTq40ix/DJWCzM66mz0wsTKPDl2A7XSLzMql4QGjiOcQHYNN9PN
 EMULXqDztHr6emdKCVanxTZofUqwjaRTH1HlLou+nMIAV4wT5Lp5vzXUNyEgG8BlhPtY
 R3GQHs++OkzoraAt9dhO4N/bQ/ISIa22jnLf4bBuhLRwrTnu6stS3sLxlsMUg4DZe5ON
 k7VRnpuypa2YSfyG9azpQYfrl5QWlHENKgY35GIQnunCdyGikghwUcOIqHZmkp04bpAo
 86EA==
X-Forwarded-Encrypted: i=1;
 AJvYcCXg9oQXw3VHJmioukci/KvSUktoou+vNpZBLDCntezWIV63ZYVDkGdDFhDXA4NjpEsabxrYnA==@debbugs.gnu.org
X-Gm-Message-State: AOJu0YwlwT7CQFxsMuOtXtC2Km3GeBhuntT/RLCFkwqSP1w6Pe+1MedH
 p56yiPFKl4FVaORk7ZkPjPmbuxrphtvjrU5xym+KRORcAvUvBBEL8PNmRg==
X-Gm-Gg: ASbGncuERqwWfrzEaozhV5uAIiletJMRn+QW5TuqJ11WXMOv5j1bwHJNVgzfjmDmzjL
 df5hOJvi2qyFSN53rlYuy6BoxEC7F1Yk0fwYO/EzG/Sp7X2ksg370ppTzsiiEwXMnyo2Qmqc9Gy
 mqQo7Wzi7+2RcQ3wbjXhlsUuLKhBUO4hTY2UjYY02jR4uhdGuYa541G3mco+BK6ZlzZRiQEAOlg
 /O0N8ulna72JDR/y20QQm8uQ1CrhysrZwpFZuhlRqNnuZvm0hJ2oVu7JrUCjgWbGCK0Lem1EQ==
X-Google-Smtp-Source: AGHT+IG17EHKuplbIaIS25ErbcGIBhCG30ksUJQQDEr61/zxsjAMvPDaTUU+/qDhLmIYXov9NetYoQ==
X-Received: by 2002:a05:6a21:7783:b0:1e0:f390:f300 with SMTP id
 adf61e73a8af0-1e0f390f3b2mr15497221637.44.1733041152091; 
 Sun, 01 Dec 2024 00:19:12 -0800 (PST)
Received: from smtpclient.apple ([2601:646:8f81:6120:71b7:718f:7faa:8436])
 by smtp.gmail.com with ESMTPSA id
 d2e1a72fcca58-72541848505sm6272644b3a.178.2024.12.01.00.19.11
 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);
 Sun, 01 Dec 2024 00:19:11 -0800 (PST)
Content-Type: text/plain;
	charset=utf-8
Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3776.700.51\))
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
From: Yuan Fu <casouri@HIDDEN>
In-Reply-To: <86h67n6dfd.fsf@HIDDEN>
Date: Sun, 1 Dec 2024 00:18:59 -0800
Content-Transfer-Encoding: quoted-printable
Message-Id: <C6B64FBC-328D-47FF-B813-02E9C09686E0@HIDDEN>
References: <3532547.LZWGnKmheA@fedora>
 <7067C127-FE4E-4E1A-8FFC-2BAB221E5B45@HIDDEN> <86h67n6dfd.fsf@HIDDEN>
To: Eli Zaretskii <eliz@HIDDEN>
X-Mailer: Apple Mail (2.3776.700.51)
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: v.pupillo@HIDDEN, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)



> On Dec 1, 2024, at 12:00=E2=80=AFAM, Eli Zaretskii <eliz@HIDDEN> =
wrote:
>=20
>> Cc: 74610 <at> debbugs.gnu.org
>> From: Yuan Fu <casouri@HIDDEN>
>> Date: Sat, 30 Nov 2024 22:01:21 -0800
>>=20
>>=20
>>=20
>>> On Nov 29, 2024, at 1:57=E2=80=AFPM, Vincenzo Pupillo =
<v.pupillo@HIDDEN> wrote:
>>>=20
>>> Ciao,
>>> following the discussion=20
>>> https://lists.gnu.org/archive/html/emacs-devel/2024-11/msg00079.html =
I would=20
>>> like to ask if it would be possible to add to emacs this new mode =
for editing=20
>>> html files alternative to mhtml-mode.
>>>=20
>>> Thank you.
>>>=20
>>> Vincenzo.<0001-Add-mhtml-ts-mode.patch>
>>=20
>> Thank you so much! This will be very helpful for others. Here=E2=80=99r=
e some comments.
>=20
> Yuan, I don't see any comments, only what I think is the original
> patch.

Ah, I guess I=E2=80=99m supposed to quote the original patch when making =
comments :-( The lines that aren=E2=80=99t prefixed by + are my =
comments.

Yuan=




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 1 Dec 2024 08:00:17 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Dec 01 03:00:17 2024
Received: from localhost ([127.0.0.1]:50145 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tHesL-0001Eh-Gt
	for submit <at> debbugs.gnu.org; Sun, 01 Dec 2024 03:00:17 -0500
Received: from eggs.gnu.org ([209.51.188.92]:35108)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <eliz@HIDDEN>) id 1tHesJ-0001Cx-EJ
 for 74610 <at> debbugs.gnu.org; Sun, 01 Dec 2024 03:00:15 -0500
Received: from fencepost.gnu.org ([2001:470:142:3::e])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <eliz@HIDDEN>)
 id 1tHesE-0000qX-0H; Sun, 01 Dec 2024 03:00:10 -0500
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org;
 s=fencepost-gnu-org; h=MIME-version:References:Subject:In-Reply-To:To:From:
 Date; bh=Fw9D5JUEE8T1TE0vpKYBLwi3bz4y3ZIeydbRhAp54QU=; b=LAIdJ9ieFXPCyBXVBI3C
 x2ngNg9EYRxP5BfoCkq13n5X9HTmpXgCuNwoJhIlKcaD1sMO6oSNjR/EKc3s/sY6Eflb2n/E1Y9hC
 jriRfG0Bap/8L6+OAoz69E+98MRwbgF/iCYbQHkHnNiF/Kf3Czi1c/GzXTBxZNXmJXlHD29oVMG4H
 4mtHXX+MHCylqvhRdZJcIRbGeRc2FjAs5GiEN1iQEzVj7Q9/19lsRiMtDmi27F4Pv/YE54fFYatqW
 NqoX5EEiYswJ91XP7jdKyNqvHjFcJLhD+SJciWMPxk47+1kgZVK16fr0ZI0cfCXPSOsue8e28UDik
 52BIXh8kycl0uw==;
Date: Sun, 01 Dec 2024 10:00:06 +0200
Message-Id: <86h67n6dfd.fsf@HIDDEN>
From: Eli Zaretskii <eliz@HIDDEN>
To: Yuan Fu <casouri@HIDDEN>
In-Reply-To: <7067C127-FE4E-4E1A-8FFC-2BAB221E5B45@HIDDEN> (message from
 Yuan Fu on Sat, 30 Nov 2024 22:01:21 -0800)
Subject: Re: bug#74610: 31.0.50;
 Submitting mhtml-ts-mode, treesitter alternative to mhtml-mode
References: <3532547.LZWGnKmheA@fedora>
 <7067C127-FE4E-4E1A-8FFC-2BAB221E5B45@HIDDEN>
MIME-version: 1.0
Content-type: text/plain; charset=utf-8
Content-Transfer-Encoding: 8bit
X-Spam-Score: -2.3 (--)
X-Debbugs-Envelope-To: 74610
Cc: v.pupillo@HIDDEN, 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -3.3 (---)

> Cc: 74610 <at> debbugs.gnu.org
> From: Yuan Fu <casouri@HIDDEN>
> Date: Sat, 30 Nov 2024 22:01:21 -0800
> 
> 
> 
> > On Nov 29, 2024, at 1:57 PM, Vincenzo Pupillo <v.pupillo@HIDDEN> wrote:
> > 
> > Ciao,
> > following the discussion 
> > https://lists.gnu.org/archive/html/emacs-devel/2024-11/msg00079.html I would 
> > like to ask if it would be possible to add to emacs this new mode for editing 
> > html files alternative to mhtml-mode.
> > 
> > Thank you.
> > 
> > Vincenzo.<0001-Add-mhtml-ts-mode.patch>
> 
> Thank you so much! This will be very helpful for others. Here’re some comments.

Yuan, I don't see any comments, only what I think is the original
patch.




Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at 74610 <at> debbugs.gnu.org:


Received: (at 74610) by debbugs.gnu.org; 1 Dec 2024 06:02:45 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Sun Dec 01 01:02:45 2024
Received: from localhost ([127.0.0.1]:49920 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tHd2a-0002xK-8Y
	for submit <at> debbugs.gnu.org; Sun, 01 Dec 2024 01:02:45 -0500
Received: from mail-pl1-f171.google.com ([209.85.214.171]:61714)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <casouri@HIDDEN>) id 1tHd2W-0002wy-RT
 for 74610 <at> debbugs.gnu.org; Sun, 01 Dec 2024 01:02:42 -0500
Received: by mail-pl1-f171.google.com with SMTP id
 d9443c01a7336-21527bb7eb0so20369465ad.3
 for <74610 <at> debbugs.gnu.org>; Sat, 30 Nov 2024 22:02:40 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1733032895; x=1733637695; darn=debbugs.gnu.org;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:from:to:cc:subject:date
 :message-id:reply-to;
 bh=i+iqHSOYSG5Pcf3cX+boARTe5iSNHixVcljrHnh8jGQ=;
 b=CzDMrlParvNRZ0UosCZNfjztEMdjU/cdN/sXyGcOIWxARt9WSeZC39SIUB9Wy00gb4
 5v0RzLQI7ZRD4Y30gCmLAV27hmGKjBc6iLiAqobqaapK3h1vEdtWZzoHBWXyjmcKdeo4
 A03KFXq1dHd4G+1nRtKuFCkx4Je844Dk/ltiiCbeBvv4vWX8b0cdGVEfUyweURNVSKZV
 B+Of4fa00fFqr4BLdloliAVTUVzoQ1q0+nK84sjgxePaF+ZKEHUQNqYRLv2oQeZ4MVF1
 nEjzQq4YUGXw+GjdGAmAKaK1S3UWJSvqzOBJpLsT1zoty2UICBJvkyn501SzmiVG1I9p
 mxLA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1733032895; x=1733637695;
 h=to:references:message-id:content-transfer-encoding:cc:date
 :in-reply-to:from:subject:mime-version:x-gm-message-state:from:to:cc
 :subject:date:message-id:reply-to;
 bh=i+iqHSOYSG5Pcf3cX+boARTe5iSNHixVcljrHnh8jGQ=;
 b=e2LVfCN6OqUndBb0vh53ekjhZ9XheBat8hUdR0vcFKljMl/rACE5FyXRJfvvYKOtkw
 yQVrbcsaymyGaEO6GAFlJrWJ0uSUBKDh9I93ina2/C9SSS+qxQO9ietPhw/kerrkcj7v
 IHhKCgP7OLoqGDCnVBn+dRhwmbN5M7DKBupqApUjO44w1ve7b3Dt8PZSEaEUdPgi4jFo
 GHq/Y/rX/MMZmNg9se3PAoNB318/HxVPZQwBSiX9PcVAu9ch9V0toDLRUXkjiSWvamIa
 v657+Rh2KtwiExC2sx2QcuPnQAyPx5xnr8s/XA9MrXRkb/YnNkYkl9Jcf4u5WtpLF6rP
 LCqg==
X-Gm-Message-State: AOJu0YzLLuzSf62e+5bjOSYVU14p73B+q+yqX/1Es4uG5SLQ3GknOme3
 zX6LExsKoURqmyHognCsnqF0mEvVMO1KZk6CVlnfv/RpLgeVngnH
X-Gm-Gg: ASbGncuz5Y9Ju5xBs5ACew2LMPkqyGnTkcoPzRPMsoge4zKYymTCPkaCwt5DzO8wIj8
 ptivCxBmjwTqUnQQF3LZa1KXLYwChMnWsUGgDazLZaqdkotj0KKlfMaeQmIrv4XePXvv7zEqPH+
 amwb2r6O2jN9hJ4LyDmxJ0cmPboSqWkTMcpWbsl7zWMhhfpLnFADvesyxlL7BTCwJObNEaSbzW/
 0Yb86VhEAA/o6pCzrxKRCS+G8FLMAE2QEeouNsTxF2SIlMFw0W+NmKcOJyxoMZl2HtZZpYHBg==
X-Google-Smtp-Source: AGHT+IGduerqz4HwLU8yqXXh5UFvotqGhUDYsYljuuphk5RBq6MApx2qcHhGgCrQMBt7SFaYrmLhYw==
X-Received: by 2002:a17:903:1c8:b0:215:44fe:1640 with SMTP id
 d9443c01a7336-21544fe1859mr93469975ad.3.1733032894823; 
 Sat, 30 Nov 2024 22:01:34 -0800 (PST)
Received: from smtpclient.apple ([2601:646:8f81:6120:71b7:718f:7faa:8436])
 by smtp.gmail.com with ESMTPSA id
 d2e1a72fcca58-72541761523sm6088939b3a.1.2024.11.30.22.01.32
 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128);
 Sat, 30 Nov 2024 22:01:33 -0800 (PST)
Content-Type: text/plain;
	charset=utf-8
Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3776.700.51\))
Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter
 alternative to mhtml-mode
From: Yuan Fu <casouri@HIDDEN>
In-Reply-To: <3532547.LZWGnKmheA@fedora>
Date: Sat, 30 Nov 2024 22:01:21 -0800
Content-Transfer-Encoding: quoted-printable
Message-Id: <7067C127-FE4E-4E1A-8FFC-2BAB221E5B45@HIDDEN>
References: <3532547.LZWGnKmheA@fedora>
To: Vincenzo Pupillo <v.pupillo@HIDDEN>
X-Mailer: Apple Mail (2.3776.700.51)
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 74610
Cc: 74610 <at> debbugs.gnu.org
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)



> On Nov 29, 2024, at 1:57=E2=80=AFPM, Vincenzo Pupillo =
<v.pupillo@HIDDEN> wrote:
>=20
> Ciao,
> following the discussion=20
> https://lists.gnu.org/archive/html/emacs-devel/2024-11/msg00079.html I =
would=20
> like to ask if it would be possible to add to emacs this new mode for =
editing=20
> html files alternative to mhtml-mode.
>=20
> Thank you.
>=20
> Vincenzo.<0001-Add-mhtml-ts-mode.patch>

Thank you so much! This will be very helpful for others. Here=E2=80=99re =
some comments.

Yuan

=46rom 8a1c792aaddf4daef2808f5a74212a2fb8b0a01e Mon Sep 17 00:00:00 2001
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
Date: Fri, 29 Nov 2024 22:48:45 +0100
Subject: [PATCH] Add mhtml-ts-mode.

New major-mode alternative to mhtml-mode, based on treesitter, for
editing files containing html, javascript and css.

* etc/NEWS: Mention the new mode.
* lisp/textmodes/mhtml-ts-mode.el: New file.
---
 etc/NEWS                        |   8 +
 lisp/textmodes/mhtml-ts-mode.el | 462 ++++++++++++++++++++++++++++++++
 2 files changed, 470 insertions(+)
 create mode 100644 lisp/textmodes/mhtml-ts-mode.el

diff --git a/etc/NEWS b/etc/NEWS
index 4d2a2c893d0..8f9a04dcf01 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -797,6 +797,14 @@ destination window is chosen using =
'display-buffer-alist'.  Example:
 =0C
 * New Modes and Packages in Emacs 31.1
=20
+** New major modes based on the tree-sitter library
+
++++
+*** New major mode 'mhtml-ts-mode'.
+An optional major mode based on the tree-sitter library for editing =
html
+files. This mode handles indentation, fontification, and commenting for
+embedded JavaScript and CSS.
+
 =0C
 * Incompatible Lisp Changes in Emacs 31.1
=20
diff --git a/lisp/textmodes/mhtml-ts-mode.el =
b/lisp/textmodes/mhtml-ts-mode.el
new file mode 100644
index 00000000000..b6b220663e3
--- /dev/null
+++ b/lisp/textmodes/mhtml-ts-mode.el
@@ -0,0 +1,100 @@
+;;; mhtml-ts-mode.el --- Major mode for HTML using tree-sitter -*- =
lexical-binding: t; -*-
+
+;; Copyright (C) 2024 Free Software Foundation, Inc.
+
+;; Author: Vincenzo Pupillo <v.pupillo@HIDDEN>
+;; Maintainer: Vincenzo Pupillo <v.pupillo@HIDDEN>
+;; Created: Nov 2024
+;; Keywords: HTML language tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; This package provides `mhtml-ts-mode' which is a major mode
+;; for editing HTML files with embedded JavaScript and CSS.
+;; Tree Sitter is used to parse each of these languages.
+;;
+;; Please note that this package requires `html-ts-mode', which
+;; registers itself as the major mode for editing HTML.
+;;
+;; This package is compatible and has been tested with the following
+;; tree-sitter grammars:
+;; * https://github.com/tree-sitter/tree-sitter-html
+;; * https://github.com/tree-sitter/tree-sitter-javascript
+;; * https://github.com/tree-sitter/tree-sitter-jsdoc
+;; * https://github.com/tree-sitter/tree-sitter-css
+;;
+;; Features
+;;
+;; * Indent
+;; * IMenu
+;; * Navigation
+;; * Which-function
+;; * Tree-sitter parser installation helper
+
+;;; Code:
+
+(require 'treesit)
+(require 'html-ts-mode)
+(require 'css-mode) ;; for embed css into html
+(require 'js) ;; for embed javascript into html
+
+(eval-when-compile
+  (require 'rx))
+
+;; Declare all native functions used by the major mode.
+;; This tells the byte-compiler where the functions are defined.
+(declare-function treesit-node-end "treesit.c")
+(declare-function treesit-node-parent "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+(declare-function treesit-node-type "treesit.c")
+(declare-function treesit-parser-create "treesit.c")
+
+;; In a multi-language major mode can be useful to have an "installer" =
to
+;; simplify the installation of the grammars supported by the =
major-mode.
+(defvar mhtml-ts-mode--language-source-alist
+  '((html . ("https://github.com/tree-sitter/tree-sitter-html"  =
"v0.23.0"))
+    (javascript . =
("https://github.com/tree-sitter/tree-sitter-javascript" "v0.23.0"))
+    (jsdoc . ("https://github.com/tree-sitter/tree-sitter-jsdoc" =
"v0.23.0"))
+    (css . ("https://github.com/tree-sitter/tree-sitter-css" =
"v0.23.0")))
+  "Treesitter language parsers required by `mhtml-ts-mode'.
+You can customize this variable if you want to stick to a specific
+commit and/or use different parsers.")
+
+(defun mhtml-ts-mode-install-parsers ()
+  "Install all the required treesitter parsers.
+`mhtml-ts-mode--language-source-alist' defines which parsers to =
install."
+  (interactive)
+  (let ((treesit-language-source-alist =
mhtml-ts-mode--language-source-alist))
+    (dolist (item mhtml-ts-mode--language-source-alist)
+      (treesit-install-language-grammar (car item)))))
+
+;;; Custom variables
+
+(defgroup mhtml-ts-mode nil
+  "Major mode for editing HTML files, based on `html-ts-mode'.
+Works with JS and CSS and for that use `js-ts-mode' and `css-ts-mode'."
+  :prefix "html-ts-mode-"
+  :group 'languages)
+
+(defcustom mhtml-ts-mode-js-css-indent-offset 2
+  "JavaScript and CSS indent spaces related to the <script> and <style> =
HTML tags.
+By default should have same value as `html-ts-mode-indent-offset'."
+  :tag "HTML javascript or css indent offset"
+  :version "31.1"
+  :type 'integer
+  :safe 'integerp)

It's not uncommon to see different indent offset for CSS and
Javascript, so it's a good idea to have separate control for them.

+
+(defvar mhtml-ts-mode--js-css-indent-offset
+  mhtml-ts-mode-js-css-indent-offset
+  "Internal copy of `mhtml-ts-mode-js-css-indent-offset'.
+The value changes, by `mhtml-ts-mode--tag-relative-indent-offset' =
according to
+the value of `mhtml-ts-mode-tag-relative-indent'.")
+
+(defun mhtml-ts-mode--tag-relative-indent-offset (sym val)
+  "Custom setter for `mhtml-ts-mode-tag-relative-indent'.
+
+Apart from setting the default value of SYM to VAL, also change the
+value of SYM in `mhtml-ts-mode' buffers to VAL.  SYM should be
+`mhtml-ts-mode-tag-relative-indent', and VAL should be t, nil or
+`ignore'.  When sym is `mhtml-ts-mode-tag-relative-indent' set the
+value of `mhtml-ts-mode--js-css-indent-offset' to 0 if VAL is t,
+otherwise to `mhtml-ts-mode-js-css-indent-offset'."
+  (set-default sym val)
+  (when (eq sym 'mhtml-ts-mode-tag-relative-indent)
+    (setq-local
+     mhtml-ts-mode--js-css-indent-offset
+     (if (eq val t)
+         mhtml-ts-mode-js-css-indent-offset
+       0))))
+
+(defcustom mhtml-ts-mode-tag-relative-indent t
+  "How <script> and <style> bodies are indented relative to the tag.
+
+When t, indentation looks like:
+
+  <script>
+    code();
+  </script>
+
+When nil, indentation of the script body starts just below the
+tag, like:
+
+  <script>
+  code();
+  </script>
+
+When `ignore', the script body starts in the first column, like:
+
+  <script>
+code();
+  </script>"
+  :type '(choice (const nil) (const t) (const ignore))
+  :safe 'symbolp
+  :set #'mhtml-ts-mode--tag-relative-indent-offset
+  :version "31.1")
+
+(defcustom mhtml-ts-mode-css-fontify-colors t
+  "Whether CSS colors should be fontified using the color as the =
background.
+If non-nil, text representing a CSS color will be fontified
+such that its background is the color itself.
+Works like `css--fontify-region'."
+  :tag "HTML colors the CSS properties values."
+  :version "31.1"
+  :type 'boolean
+  :safe 'booleanp)
+
+;; To enable some basic treesiter functionality, you should define
+;; a function that recognizes which grammar is used at-point.
+;; This function should be assigned to =
`treesit-language-at-point-function'
+(defun mhtml-ts-mode--language-at-point (point)
+  "Return the language at POINT assuming the point is within a HTML =
buffer."
+  (let* ((node (treesit-node-at point 'html))
+         (parent (treesit-node-parent node))
+         (node-query (format "(%s (%s))"
+                             (treesit-node-type parent)
+                             (treesit-node-type node))))
+    (cond
+     ((string-equal "(script_element (raw_text))" node-query) =
'javascript)
+     ((string-equal "(style_element (raw_text))" node-query) 'css)
+     (t 'html))))
+
+;; Sometimes you need to override some property attached to a node.
+;; The signature of the function should be conforming to signature
+;; QUERY-SPEC required by `treesit-font-lock-rules'.

"property attached to a node" is vague. I would just say

;; Custom font-lock function that's used to apply color to css color
;; values. This function is used below where we define font-lock rules.

+(defun mhtml-ts-mode--colorize-css-value (node override start end &rest =
_)
+  "Colorize CSS property value like `css--fontify-region'.
+For NODE, OVERRIDE, START, and END, see `treesit-font-lock-rules'."
+  (if (and mhtml-ts-mode-css-fontify-colors
+           (string-equal "plain_value" (treesit-node-type node)))
+      (let ((color (css--compute-color start (treesit-node-text node =
t))))
+        (when color
+          (with-silent-modifications
+            (add-text-properties
+             (treesit-node-start node) (treesit-node-end node)
+             (list 'face (list :background color
+                               :foreground (readable-foreground-color
+                                            color)
+                               :box '(:line-width -1)))))))
+    (treesit-fontify-with-override
+     (treesit-node-start node) (treesit-node-end node)
+     'font-lock-variable-name-face
+     override start end)))
+
+;; Embedded languages =E2=80=8B=E2=80=8Bshould be indented according to =
the language
+;; that embeds them.
+;; This function signature complies with `treesit-simple-indent-rules'
+;; ANCHOR.
+(defun mhtml-ts-mode--js-css-tag-bol (_node _parent &rest _)
+  "Find the first non-space characters of html tags <script> or =
<style>.
+Return `line-beginning-position' when `treesit-node-at' is html, or
+`mhtml-ts-mode-tag-relative-indent' is equal to ignore.
+NODE and PARENT are ignored."
+  (if (or (eq (treesit-language-at (point)) 'html)
+          (eq mhtml-ts-mode-tag-relative-indent 'ignore))
+      (line-beginning-position)
+    ;; Ok, we are in js or css block.
+    (save-excursion
+      (re-search-backward "<script.*>\\|<style.*>" nil t))))
+
+;; Treesit supports 4 level of decoration, `treesit-font-lock-level'
+;; define which level use.  Major-modes categorize their fontification
+;; features, these categories are defined by `treesit-font-lock-rules' =
of
+;; each major-mode using :feature keyword.
+;; In a multiple language major-mode it's a good idea to provvide, for =
each
+;; level, the union of the :feature of the same level.

"which level to use", "Major modes", and "provide"

+(defvar mhtml-ts-mode--feature-list
+  '(;; level 1
+    (;; common
+     comment definition
+     ;; JS specific
+     document
+     ;; CSS specific
+     query selector)
+    ;; level 2
+    (keyword name property string type)
+    ;; level 3
+    (;; common
+     attribute assignment constant escape-sequence
+     base-clause literal variable-name variable
+     ;; Javascript specific
+     jsx number pattern string-interpolation)
+    ;; level 4
+    (bracket delimiter error operator function)))
+
+;; In order to support wich-fuction-mode we should define

"which-function-mode"

+;; a function that return the defun name.
+;; In a multilingual treesit mode, this can be implemented simply by
+;; calling language-specific functions.
+(defun mhtml-ts-mode--defun-name (node)
+  "Return the defun name of NODE.
+Return nil if there is no name or if NODE is not a defun node."
+  ;; (message "node type ""%s""" (treesit-node-type node))
+  (let ((lang (mhtml-ts-mode--language-at-point (point))))
+    (cond
+     ((eq lang 'html) (html-ts-mode--defun-name node))
+     ((eq lang 'javascript) (js--treesit-defun-name node))
+     ((eq lang 'css) (css--treesit-defun-name node)))))
+
+(define-derived-mode mhtml-ts-mode html-mode
+  '("HTML+" (:eval (let ((lang (mhtml-ts-mode--language-at-point =
(point))))
+                     (cond ((eq lang 'html) "")
+                           ((eq lang 'javascript) "JS")
+                           ((eq lang 'css) "CSS")))))
+  "Major mode for editing HTML with embedded JavaScript and CSS.
+Powered by tree-sitter."
+  (if (not (and
+            (treesit-ready-p 'html)
+            (treesit-ready-p 'javascript)
+            (treesit-ready-p 'css)))
+      (error "Tree-sitter parsers for HTML isn't
+    available.  You can install the parsers with M-x
+    `mhtml-ts-mode-install-parsers'")
+
+    ;; When an language is embedded, you should initialize some =
variable
+    ;; just like it's done in the original mode.
+
+    ;; Comment.
+    ;; indenting settings for js-ts-mode.
+    (c-ts-common-comment-setup)
+    (setq-local comment-multi-line t)
+
+    ;; Font-lock.
+
+    ;; There are two kind of treesitter parser:
+    ;; 1. global parser
+    ;; 2. local parser
+    ;; The global parser considers each piece of text,
+    ;; in a multilingual buffer, as if it were a single buffer in its
+    ;; own language. Local parsers, on the other hand, consider each
+    ;; piece of text, in a multilingual buffer, as if they were
+    ;; separate buffers.
+    ;; In a multilingual buffer you should create only global ones.
+    ;; Local ones are created automatically.
+    ;; Warning: do not create a local parser! It may cause side
+    ;; effects that are difficult to handle.
+
+    ;; There are two types of treesitter parsers:
+    ;; 1. global parsers
+    ;; 2. local parsers
+    ;; A global parser treats each piece of text,
+    ;; in a multilingual buffer, as if it were a single buffer in its
+    ;; language. Local parser, on the other hand, treat each
+    ;; piece of text, in a multilingual buffer, as if they were =
separate buffers.
+    ;; In a multilingual buffer you should only create global ones.
+    ;; The local ones are created automatically.
+    ;; Warning: do not create a local parser! It may cause side effects =
that are difficult to handle.

Seems like a duplicate? And I want to highlight the fact that we're
talking about embedded parsers here, so I would say:

There are two ways to handle embedded code:
1. Use a single parser for all the embedded code in the buffer. In
this case, the embedded code blocks are concatenated together and are
seen as a single continuous document to the parser.
2. Each embedded code block gets its own parser. Each parser only sees
that particular code block.

If you go with 2 for a language, the local parsers are created and
destroyed automatically by Emacs. So don't create a global parser for
that embedded language here.

+
+    ;; Create the parsers, only the global one.

"only global ones", I think

+    ;; jsdoc is a local parser, don't create a parser for it.
+    (treesit-parser-create 'css)
+    (treesit-parser-create 'javascript)
+
+    ;; jsdoc is not mandatory for js-ts-mode, so we respect this by
+    ;; adding jsdoc range rules only when jsdoc is available.
+    (if (treesit-ready-p 'jsdoc t)
+        (setq-local treesit-range-settings
+                    (treesit-range-rules
+                     :embed 'javascript
+                     :host 'html
+                     :offset '(1 . -1)
+                     '((script_element
+                        (start_tag (tag_name))
+                        (raw_text) @cap))
+
+                     :embed 'jsdoc
+                     :host 'javascript
+                     :local t
+                     `(((comment) @cap
+                        (:match ,js--treesit-jsdoc-beginning-regexp =
@cap)))
+
+                     :embed 'css
+                     :host 'html
+                     :offset '(1 . -1)
+                     '((style_element
+                        (start_tag (tag_name))
+                        (raw_text) @cap))))
+      (setq-local treesit-range-settings
+                  (treesit-range-rules
+                   :embed 'javascript
+                   :host 'html
+                   :offset '(1 . -1)
+                   '((script_element
+                      (start_tag (tag_name))
+                      (raw_text) @cap))
+
+                   :embed 'css
+                   :host 'html
+                   :offset '(1 . -1)
+                   '((style_element
+                      (start_tag (tag_name))
+                      (raw_text) @cap)))))

You can create range rules for each language and append them, that way
you don't need to duplicate the code here. It's just like the
font-lock settings.

+
+    ;; Many treesit fuctions need to know the language at-point.
+    ;; So you should define such a function.
+    (setq-local treesit-language-at-point-function =
#'mhtml-ts-mode--language-at-point)
+
+    ;; Indent.
+
+    ;; Since mhtl-ts-mode inherits indentation rules from html-ts-mode, =
js
+    ;; and css, if you want to change the offset you have to act on the
+    ;; *-offset variables defined for those languages.
+
+    ;; JavaScript and CSS must be indented relative to their code =
block.
+    ;; This is done by inserting a special rule before the normal
+    ;; indentation rules of these languages.
+    ;; The value of mhtml-ts-mode--js-css-indent-offset changes based =
on
+    ;; mhtml-ts-mode-tag-relative-indent and can be used to indent
+    ;; JavaScript and CSS code relative to the HTML that contains them,
+    ;; just like in mhtml-mode.
+    (setq-local treesit-simple-indent-rules
+                (append html-ts-mode--indent-rules
+                        ;; Extended rules for js and css, to
+                        ;; indent appropriately when injected
+                        ;; into html
+                        `((javascript ((parent-is "program")
+                                       mhtml-ts-mode--js-css-tag-bol
+                                       =
mhtml-ts-mode--js-css-indent-offset)
+                                      ,@(cdr (car =
js--treesit-indent-rules))))
+                        `((css ((parent-is "stylesheet")
+                                mhtml-ts-mode--js-css-tag-bol
+                                mhtml-ts-mode--js-css-indent-offset)
+                               ,@(cdr (car =
css--treesit-indent-rules))))))
+    ;; Navigation.
+
+    ;; This regular expression tells treesit how to match the node type
+    ;; of defun nodes.
+    ;; Used by `treesit-beginning-of-defun' and friends for
+    ;; navigations.
+    (setq-local treesit-defun-type-regexp
+                (rx (or
+                     ;; Javascript
+                     "class_declaration"
+                     "method_definition"
+                     "function_declaration"
+                     "lexical_declaration"
+                     ;; HTML
+                     "element"
+                     ;; CSS
+                     "rule_set")))

You can actually define a defun "thing" in treesit-thing-setting, and
it should work the same.

+    ;; This is for finding defun name, it's used by IMenu as default
+    ;; function no specific functions are defined.
+    (setq-local treesit-defun-name-function =
#'mhtml-ts-mode--defun-name)
+
+    ;; Define what are 'thing' for treesit.
+    ;; 'Thing' is a symbol representing the thing, like `defun', =
`sexp', or
+    ;; `sentence'.
+    (setq-local treesit-thing-settings
+                `((html
+                   (sexp ,(regexp-opt '("element"
+                                        "text"
+                                        "attribute"
+                                        "value")))
+                   (sentence "tag")
+                   (text ,(regexp-opt '("comment" "text"))))
+                  (javascript
+                   (sexp ,(js--regexp-opt-symbol =
js--treesit-sexp-nodes))
+                   (sentence ,(js--regexp-opt-symbol =
js--treesit-sentence-nodes))
+                   (text ,(js--regexp-opt-symbol '("comment"
+                                                   =
"string_fragment"))))))
+
+    ;; Font-lock.
+
+    ;; In a multi-language scenario, font lock settings are usually a
+    ;; concatenation of language rules. As you can see, it is possible
+    ;; to extend/modify the default rule or use a different set of
+    ;; rules. See `php-ts-mode--custom-html-font-lock-settings' for =
more
+    ;; advanced usage.
+    (setq-local treesit-font-lock-settings
+                (append html-ts-mode--font-lock-settings
+                        js--treesit-font-lock-settings
+                        (append
+                         ;; Rule for coloring CSS property values.
+                         ;; Placed before `css--treesit-settings'
+                         ;; to win against the same rule contained =
therein.
+                         (treesit-font-lock-rules
+                          :language 'css
+                          :override t
+                          :feature 'variable
+                          '((plain_value) =
@mhtml-ts-mode--colorize-css-value))
+                         css--treesit-settings)))
+
+    ;; Tells treesit the list of features to fontify.
+    (setq-local treesit-font-lock-feature-list =
mhtml-ts-mode--feature-list)
+
+    ;; Imenu
+
+    ;; Setup Imenu: if no function is specified, try to find an object
+    ;; using `treesit-defun-name-function'.
+    ;; TODO: we need to see if it is possible to extend Imenu to
+    ;; embedded languages =E2=80=8B=E2=80=8Bas well.
+    (setq-local treesit-simple-imenu-settings
+                `(("Element" "\\`tag_name\\'" nil nil)))
+
+    ;; This should be the last thing to do.
+    ;; Treesit tries to find out what the primary language is, but it =
is better
+    ;; to say it explicitly.

Correction: multi-language modes must set the primary parser
explicitly, the auto-guessing trick only works for single-language
modes. You can also move this line next to where you created the other
parsers, for better readability.

+    (setq-local treesit-primary-parser (treesit-parser-create 'html))

+    (treesit-font-lock-recompute-features)

You don't need to call treesit-font-lock-recompute-features,
treesit-major-mode-setup will do that for you.

+    (treesit-major-mode-setup)))
+
+(when (and (treesit-ready-p 'html) (treesit-ready-p 'javascript) =
(treesit-ready-p 'css))
+  (add-to-list
+   'auto-mode-alist '("\\.[sx]?html?\\(\\.[a-zA-Z_]+\\)?\\'" . =
mhtml-ts-mode)))
+
+(provide 'mhtml-ts-mode)
+;;; mhtml-ts-mode.el ends here
--=20
2.47.1





Information forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.

Message received at submit <at> debbugs.gnu.org:


Received: (at submit) by debbugs.gnu.org; 29 Nov 2024 21:57:19 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Fri Nov 29 16:57:18 2024
Received: from localhost ([127.0.0.1]:44641 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1tH8zF-00038O-B9
	for submit <at> debbugs.gnu.org; Fri, 29 Nov 2024 16:57:18 -0500
Received: from lists.gnu.org ([209.51.188.17]:58814)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <v.pupillo@HIDDEN>) id 1tH8zC-00038C-5e
 for submit <at> debbugs.gnu.org; Fri, 29 Nov 2024 16:57:15 -0500
Received: from eggs.gnu.org ([2001:470:142:3::10])
 by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <v.pupillo@HIDDEN>)
 id 1tH8zB-0001j5-Ic
 for bug-gnu-emacs@HIDDEN; Fri, 29 Nov 2024 16:57:13 -0500
Received: from mail-ed1-x52d.google.com ([2a00:1450:4864:20::52d])
 by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128)
 (Exim 4.90_1) (envelope-from <v.pupillo@HIDDEN>)
 id 1tH8z8-0002KQ-Lo
 for bug-gnu-emacs@HIDDEN; Fri, 29 Nov 2024 16:57:13 -0500
Received: by mail-ed1-x52d.google.com with SMTP id
 4fb4d7f45d1cf-5d0ca0f67b6so154789a12.3
 for <bug-gnu-emacs@HIDDEN>; Fri, 29 Nov 2024 13:57:10 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=gmail.com; s=20230601; t=1732917429; x=1733522229; darn=gnu.org;
 h=content-transfer-encoding:mime-version:message-id:date:subject:to
 :from:from:to:cc:subject:date:message-id:reply-to;
 bh=TSZHXXS2F6tL/d5AC4xAGWw8oryh1RYjICXZxAucJNc=;
 b=WIsvZAyp/uFYH1LZPUdGRy4mNurJZu+y+LYLe6E07nnWrzNtOy1j+g9fGrLZsPqER4
 XxYL18abNouC7eSKSWV3zcimM1VP+SM7dCOzr28nei4Ay+rD6vknFvi3AQCnk2+PSxfr
 Rk2ABmpZoW3e3FTC37ek+uJIM/mIqpg4o6JAjM8fT4q5P25kNiHNV3kx1lDDR+2BcXEx
 CU/2RMW4XryRlfOximvxiz9RutYqEyZO7xc7kGN25cDWT1cwvUFHgKUt7hre6r9d0OpY
 vhpOcx/wr2N5M4IZRZQYYdHO/QsGQZdBHeRCJ2f2T9w2LY6hwPYytlN8AIs6imVGp1Bd
 RGPg==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20230601; t=1732917429; x=1733522229;
 h=content-transfer-encoding:mime-version:message-id:date:subject:to
 :from:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to;
 bh=TSZHXXS2F6tL/d5AC4xAGWw8oryh1RYjICXZxAucJNc=;
 b=USNOtTrN1l6afBjTau2p2+SKH9GEITJ/InkbuCc74V3tvDvgkxJFEb9HSHlgCOkKLs
 C0k4YRbzrl/eZNmdTip6B9eRpHtszNk0ujeCjtIQIBu25/AI5c6WF1rafBEqku1GZ3Hc
 1Ym1jX+R+2Nzs0wTsH6t4sn0TPGc2kB8VqJq2jMmIxNgQW5n5vUAb/sO554wgPeLUoOD
 zN3wRkgMTOp49mNOXxnZtv9m/j+PWEtzUGJaWEh7cZcB9d4nuo78wemYTa5Fz5u21cUK
 RbF3G+CdfJoAj97tMMn3n4oDkZfv9aeQ5/zHyrDBIGpkb2GCMr7nP4/sDvsVLZKkzndK
 6RxQ==
X-Gm-Message-State: AOJu0Yxlyikx3h3fT7BvZnUXTO58/ADJIq+ZmhNNMrSxQp1AUYGZZTe8
 7ErBzb8DsBFLuQOJ/dOY1Sqd0+cGUsOIdjRvxnJz02TviNodqj/Rx0Frfg==
X-Gm-Gg: ASbGnctCSqoHDcDm52rX9Do8aJictERB3WIJdnYCm7G0dCrMOUwhKy5QxYO3rtNBJ8a
 tyRwf5+03XT3r/+YEhvfz9F5Y60iMgF0Ohk5w9n9lxETynKfaleKu5GIX9Mir0t88LDdDD97ypE
 11slbKFxlrqte3XjIMjJIqqo46pq5HXAB74P5Hh2Z0MNWN/KZBR5lvVZ0GHSsz4B5m68nR0Ggmg
 TVZuILhyNxGCAudEmDJt32ncJHvtAFZSUAn9NyoSe0mVftEtLNNKZb6jnBs7WIT+hDieb5IuQ2g
 yLesY4H5ghv33w==
X-Google-Smtp-Source: AGHT+IEIvr0y9vWyKTO6diUHgQPrjGnbVQdgdIotmvBPbH/h3qga1vL3sFlvcfmYnazMudLtyDfECA==
X-Received: by 2002:a17:906:cc5e:b0:aa5:317b:3a0 with SMTP id
 a640c23a62f3a-aa58108f995mr1148569566b.57.1732917428770; 
 Fri, 29 Nov 2024 13:57:08 -0800 (PST)
Received: from fedora.localnet (2-230-139-124.ip202.fastwebnet.it.
 [2.230.139.124]) by smtp.gmail.com with ESMTPSA id
 a640c23a62f3a-aa5996c1207sm215626866b.22.2024.11.29.13.57.06
 for <bug-gnu-emacs@HIDDEN>
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Fri, 29 Nov 2024 13:57:07 -0800 (PST)
From: Vincenzo Pupillo <v.pupillo@HIDDEN>
To: Bug Emacs <bug-gnu-emacs@HIDDEN>
Subject: 31.0.50; Submitting mhtml-ts-mode,
 treesitter alternative to mhtml-mode
Date: Fri, 29 Nov 2024 22:57:05 +0100
Message-ID: <3532547.LZWGnKmheA@fedora>
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="nextPart7111894.9J7NaK4W3v"
Content-Transfer-Encoding: 7Bit
Received-SPF: pass client-ip=2a00:1450:4864:20::52d;
 envelope-from=v.pupillo@HIDDEN; helo=mail-ed1-x52d.google.com
X-Spam_score_int: -20
X-Spam_score: -2.1
X-Spam_bar: --
X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,
 DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001,
 RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001,
 WEIRD_QUOTING=0.001 autolearn=ham autolearn_force=no
X-Spam_action: no action
X-Spam-Score: -1.3 (-)
X-Debbugs-Envelope-To: submit
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -2.3 (--)

This is a multi-part message in MIME format.

--nextPart7111894.9J7NaK4W3v
Content-Transfer-Encoding: 7Bit
Content-Type: text/plain; charset="utf-8"

Ciao,
following the discussion 
https://lists.gnu.org/archive/html/emacs-devel/2024-11/msg00079.html I would 
like to ask if it would be possible to add to emacs this new mode for editing 
html files alternative to mhtml-mode.

Thank you.

Vincenzo.
--nextPart7111894.9J7NaK4W3v
Content-Disposition: attachment; filename="0001-Add-mhtml-ts-mode.patch"
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-patch; charset="UTF-8";
 name="0001-Add-mhtml-ts-mode.patch"

=46rom 8a1c792aaddf4daef2808f5a74212a2fb8b0a01e Mon Sep 17 00:00:00 2001
=46rom: Vincenzo Pupillo <v.pupillo@HIDDEN>
Date: Fri, 29 Nov 2024 22:48:45 +0100
Subject: [PATCH] Add mhtml-ts-mode.

New major-mode alternative to mhtml-mode, based on treesitter, for
editing files containing html, javascript and css.

* etc/NEWS: Mention the new mode.
* lisp/textmodes/mhtml-ts-mode.el: New file.
=2D--
 etc/NEWS                        |   8 +
 lisp/textmodes/mhtml-ts-mode.el | 462 ++++++++++++++++++++++++++++++++
 2 files changed, 470 insertions(+)
 create mode 100644 lisp/textmodes/mhtml-ts-mode.el

diff --git a/etc/NEWS b/etc/NEWS
index 4d2a2c893d0..8f9a04dcf01 100644
=2D-- a/etc/NEWS
+++ b/etc/NEWS
@@ -797,6 +797,14 @@ destination window is chosen using 'display-buffer-ali=
st'.  Example:
 =0C
 * New Modes and Packages in Emacs 31.1
=20
+** New major modes based on the tree-sitter library
+
++++
+*** New major mode 'mhtml-ts-mode'.
+An optional major mode based on the tree-sitter library for editing html
+files. This mode handles indentation, fontification, and commenting for
+embedded JavaScript and CSS.
+
 =0C
 * Incompatible Lisp Changes in Emacs 31.1
=20
diff --git a/lisp/textmodes/mhtml-ts-mode.el b/lisp/textmodes/mhtml-ts-mode=
=2Eel
new file mode 100644
index 00000000000..b6b220663e3
=2D-- /dev/null
+++ b/lisp/textmodes/mhtml-ts-mode.el
@@ -0,0 +1,462 @@
+;;; mhtml-ts-mode.el --- Major mode for HTML using tree-sitter -*- lexical=
=2Dbinding: t; -*-
+
+;; Copyright (C) 2024 Free Software Foundation, Inc.
+
+;; Author: Vincenzo Pupillo <v.pupillo@HIDDEN>
+;; Maintainer: Vincenzo Pupillo <v.pupillo@HIDDEN>
+;; Created: Nov 2024
+;; Keywords: HTML language tree-sitter
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; This package provides `mhtml-ts-mode' which is a major mode
+;; for editing HTML files with embedded JavaScript and CSS.
+;; Tree Sitter is used to parse each of these languages.
+;;
+;; Please note that this package requires `html-ts-mode', which
+;; registers itself as the major mode for editing HTML.
+;;
+;; This package is compatible and has been tested with the following
+;; tree-sitter grammars:
+;; * https://github.com/tree-sitter/tree-sitter-html
+;; * https://github.com/tree-sitter/tree-sitter-javascript
+;; * https://github.com/tree-sitter/tree-sitter-jsdoc
+;; * https://github.com/tree-sitter/tree-sitter-css
+;;
+;; Features
+;;
+;; * Indent
+;; * IMenu
+;; * Navigation
+;; * Which-function
+;; * Tree-sitter parser installation helper
+
+;;; Code:
+
+(require 'treesit)
+(require 'html-ts-mode)
+(require 'css-mode) ;; for embed css into html
+(require 'js) ;; for embed javascript into html
+
+(eval-when-compile
+  (require 'rx))
+
+;; Declare all native functions used by the major mode.
+;; This tells the byte-compiler where the functions are defined.
+(declare-function treesit-node-end "treesit.c")
+(declare-function treesit-node-parent "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+(declare-function treesit-node-type "treesit.c")
+(declare-function treesit-parser-create "treesit.c")
+
+;; In a multi-language major mode can be useful to have an "installer" to
+;; simplify the installation of the grammars supported by the major-mode.
+(defvar mhtml-ts-mode--language-source-alist
+  '((html . ("https://github.com/tree-sitter/tree-sitter-html"  "v0.23.0"))
+    (javascript . ("https://github.com/tree-sitter/tree-sitter-javascript"=
 "v0.23.0"))
+    (jsdoc . ("https://github.com/tree-sitter/tree-sitter-jsdoc" "v0.23.0"=
))
+    (css . ("https://github.com/tree-sitter/tree-sitter-css" "v0.23.0")))
+  "Treesitter language parsers required by `mhtml-ts-mode'.
+You can customize this variable if you want to stick to a specific
+commit and/or use different parsers.")
+
+(defun mhtml-ts-mode-install-parsers ()
+  "Install all the required treesitter parsers.
+`mhtml-ts-mode--language-source-alist' defines which parsers to install."
+  (interactive)
+  (let ((treesit-language-source-alist mhtml-ts-mode--language-source-alis=
t))
+    (dolist (item mhtml-ts-mode--language-source-alist)
+      (treesit-install-language-grammar (car item)))))
+
+;;; Custom variables
+
+(defgroup mhtml-ts-mode nil
+  "Major mode for editing HTML files, based on `html-ts-mode'.
+Works with JS and CSS and for that use `js-ts-mode' and `css-ts-mode'."
+  :prefix "html-ts-mode-"
+  :group 'languages)
+
+(defcustom mhtml-ts-mode-js-css-indent-offset 2
+  "JavaScript and CSS indent spaces related to the <script> and <style> HT=
ML tags.
+By default should have same value as `html-ts-mode-indent-offset'."
+  :tag "HTML javascript or css indent offset"
+  :version "31.1"
+  :type 'integer
+  :safe 'integerp)
+
+(defvar mhtml-ts-mode--js-css-indent-offset
+  mhtml-ts-mode-js-css-indent-offset
+  "Internal copy of `mhtml-ts-mode-js-css-indent-offset'.
+The value changes, by `mhtml-ts-mode--tag-relative-indent-offset' accordin=
g to
+the value of `mhtml-ts-mode-tag-relative-indent'.")
+
+(defun mhtml-ts-mode--tag-relative-indent-offset (sym val)
+  "Custom setter for `mhtml-ts-mode-tag-relative-indent'.
+
+Apart from setting the default value of SYM to VAL, also change the
+value of SYM in `mhtml-ts-mode' buffers to VAL.  SYM should be
+`mhtml-ts-mode-tag-relative-indent', and VAL should be t, nil or
+`ignore'.  When sym is `mhtml-ts-mode-tag-relative-indent' set the
+value of `mhtml-ts-mode--js-css-indent-offset' to 0 if VAL is t,
+otherwise to `mhtml-ts-mode-js-css-indent-offset'."
+  (set-default sym val)
+  (when (eq sym 'mhtml-ts-mode-tag-relative-indent)
+    (setq-local
+     mhtml-ts-mode--js-css-indent-offset
+     (if (eq val t)
+         mhtml-ts-mode-js-css-indent-offset
+       0))))
+
+(defcustom mhtml-ts-mode-tag-relative-indent t
+  "How <script> and <style> bodies are indented relative to the tag.
+
+When t, indentation looks like:
+
+  <script>
+    code();
+  </script>
+
+When nil, indentation of the script body starts just below the
+tag, like:
+
+  <script>
+  code();
+  </script>
+
+When `ignore', the script body starts in the first column, like:
+
+  <script>
+code();
+  </script>"
+  :type '(choice (const nil) (const t) (const ignore))
+  :safe 'symbolp
+  :set #'mhtml-ts-mode--tag-relative-indent-offset
+  :version "31.1")
+
+(defcustom mhtml-ts-mode-css-fontify-colors t
+  "Whether CSS colors should be fontified using the color as the backgroun=
d.
+If non-nil, text representing a CSS color will be fontified
+such that its background is the color itself.
+Works like `css--fontify-region'."
+  :tag "HTML colors the CSS properties values."
+  :version "31.1"
+  :type 'boolean
+  :safe 'booleanp)
+
+;; To enable some basic treesiter functionality, you should define
+;; a function that recognizes which grammar is used at-point.
+;; This function should be assigned to `treesit-language-at-point-function'
+(defun mhtml-ts-mode--language-at-point (point)
+  "Return the language at POINT assuming the point is within a HTML buffer=
=2E"
+  (let* ((node (treesit-node-at point 'html))
+         (parent (treesit-node-parent node))
+         (node-query (format "(%s (%s))"
+                             (treesit-node-type parent)
+                             (treesit-node-type node))))
+    (cond
+     ((string-equal "(script_element (raw_text))" node-query) 'javascript)
+     ((string-equal "(style_element (raw_text))" node-query) 'css)
+     (t 'html))))
+
+;; Sometimes you need to override some property attached to a node.
+;; The signature of the function should be conforming to signature
+;; QUERY-SPEC required by `treesit-font-lock-rules'.
+(defun mhtml-ts-mode--colorize-css-value (node override start end &rest _)
+  "Colorize CSS property value like `css--fontify-region'.
+For NODE, OVERRIDE, START, and END, see `treesit-font-lock-rules'."
+  (if (and mhtml-ts-mode-css-fontify-colors
+           (string-equal "plain_value" (treesit-node-type node)))
+      (let ((color (css--compute-color start (treesit-node-text node t))))
+        (when color
+          (with-silent-modifications
+            (add-text-properties
+             (treesit-node-start node) (treesit-node-end node)
+             (list 'face (list :background color
+                               :foreground (readable-foreground-color
+                                            color)
+                               :box '(:line-width -1)))))))
+    (treesit-fontify-with-override
+     (treesit-node-start node) (treesit-node-end node)
+     'font-lock-variable-name-face
+     override start end)))
+
+;; Embedded languages =E2=80=8B=E2=80=8Bshould be indented according to th=
e language
+;; that embeds them.
+;; This function signature complies with `treesit-simple-indent-rules'
+;; ANCHOR.
+(defun mhtml-ts-mode--js-css-tag-bol (_node _parent &rest _)
+  "Find the first non-space characters of html tags <script> or <style>.
+Return `line-beginning-position' when `treesit-node-at' is html, or
+`mhtml-ts-mode-tag-relative-indent' is equal to ignore.
+NODE and PARENT are ignored."
+  (if (or (eq (treesit-language-at (point)) 'html)
+          (eq mhtml-ts-mode-tag-relative-indent 'ignore))
+      (line-beginning-position)
+    ;; Ok, we are in js or css block.
+    (save-excursion
+      (re-search-backward "<script.*>\\|<style.*>" nil t))))
+
+;; Treesit supports 4 level of decoration, `treesit-font-lock-level'
+;; define which level use.  Major-modes categorize their fontification
+;; features, these categories are defined by `treesit-font-lock-rules' of
+;; each major-mode using :feature keyword.
+;; In a multiple language major-mode it's a good idea to provvide, for each
+;; level, the union of the :feature of the same level.
+(defvar mhtml-ts-mode--feature-list
+  '(;; level 1
+    (;; common
+     comment definition
+     ;; JS specific
+     document
+     ;; CSS specific
+     query selector)
+    ;; level 2
+    (keyword name property string type)
+    ;; level 3
+    (;; common
+     attribute assignment constant escape-sequence
+     base-clause literal variable-name variable
+     ;; Javascript specific
+     jsx number pattern string-interpolation)
+    ;; level 4
+    (bracket delimiter error operator function)))
+
+;; In order to support wich-fuction-mode we should define
+;; a function that return the defun name.
+;; In a multilingual treesit mode, this can be implemented simply by
+;; calling language-specific functions.
+(defun mhtml-ts-mode--defun-name (node)
+  "Return the defun name of NODE.
+Return nil if there is no name or if NODE is not a defun node."
+  ;; (message "node type ""%s""" (treesit-node-type node))
+  (let ((lang (mhtml-ts-mode--language-at-point (point))))
+    (cond
+     ((eq lang 'html) (html-ts-mode--defun-name node))
+     ((eq lang 'javascript) (js--treesit-defun-name node))
+     ((eq lang 'css) (css--treesit-defun-name node)))))
+
+(define-derived-mode mhtml-ts-mode html-mode
+  '("HTML+" (:eval (let ((lang (mhtml-ts-mode--language-at-point (point))))
+                     (cond ((eq lang 'html) "")
+                           ((eq lang 'javascript) "JS")
+                           ((eq lang 'css) "CSS")))))
+  "Major mode for editing HTML with embedded JavaScript and CSS.
+Powered by tree-sitter."
+  (if (not (and
+            (treesit-ready-p 'html)
+            (treesit-ready-p 'javascript)
+            (treesit-ready-p 'css)))
+      (error "Tree-sitter parsers for HTML isn't
+    available.  You can install the parsers with M-x
+    `mhtml-ts-mode-install-parsers'")
+
+    ;; When an language is embedded, you should initialize some variable
+    ;; just like it's done in the original mode.
+
+    ;; Comment.
+    ;; indenting settings for js-ts-mode.
+    (c-ts-common-comment-setup)
+    (setq-local comment-multi-line t)
+
+    ;; Font-lock.
+
+    ;; There are two kind of treesitter parser:
+    ;; 1. global parser
+    ;; 2. local parser
+    ;; The global parser considers each piece of text,
+    ;; in a multilingual buffer, as if it were a single buffer in its
+    ;; own language. Local parsers, on the other hand, consider each
+    ;; piece of text, in a multilingual buffer, as if they were
+    ;; separate buffers.
+    ;; In a multilingual buffer you should create only global ones.
+    ;; Local ones are created automatically.
+    ;; Warning: do not create a local parser! It may cause side
+    ;; effects that are difficult to handle.
+
+    ;; There are two types of treesitter parsers:
+    ;; 1. global parsers
+    ;; 2. local parsers
+    ;; A global parser treats each piece of text,
+    ;; in a multilingual buffer, as if it were a single buffer in its
+    ;; language. Local parser, on the other hand, treat each
+    ;; piece of text, in a multilingual buffer, as if they were separate b=
uffers.
+    ;; In a multilingual buffer you should only create global ones.
+    ;; The local ones are created automatically.
+    ;; Warning: do not create a local parser! It may cause side effects th=
at are difficult to handle.
+
+    ;; Create the parsers, only the global one.
+    ;; jsdoc is a local parser, don't create a parser for it.
+    (treesit-parser-create 'css)
+    (treesit-parser-create 'javascript)
+
+    ;; jsdoc is not mandatory for js-ts-mode, so we respect this by
+    ;; adding jsdoc range rules only when jsdoc is available.
+    (if (treesit-ready-p 'jsdoc t)
+        (setq-local treesit-range-settings
+                    (treesit-range-rules
+                     :embed 'javascript
+                     :host 'html
+                     :offset '(1 . -1)
+                     '((script_element
+                        (start_tag (tag_name))
+                        (raw_text) @cap))
+
+                     :embed 'jsdoc
+                     :host 'javascript
+                     :local t
+                     `(((comment) @cap
+                        (:match ,js--treesit-jsdoc-beginning-regexp @cap)))
+
+                     :embed 'css
+                     :host 'html
+                     :offset '(1 . -1)
+                     '((style_element
+                        (start_tag (tag_name))
+                        (raw_text) @cap))))
+      (setq-local treesit-range-settings
+                  (treesit-range-rules
+                   :embed 'javascript
+                   :host 'html
+                   :offset '(1 . -1)
+                   '((script_element
+                      (start_tag (tag_name))
+                      (raw_text) @cap))
+
+                   :embed 'css
+                   :host 'html
+                   :offset '(1 . -1)
+                   '((style_element
+                      (start_tag (tag_name))
+                      (raw_text) @cap)))))
+
+    ;; Many treesit fuctions need to know the language at-point.
+    ;; So you should define such a function.
+    (setq-local treesit-language-at-point-function #'mhtml-ts-mode--langua=
ge-at-point)
+
+    ;; Indent.
+
+    ;; Since mhtl-ts-mode inherits indentation rules from html-ts-mode, js
+    ;; and css, if you want to change the offset you have to act on the
+    ;; *-offset variables defined for those languages.
+
+    ;; JavaScript and CSS must be indented relative to their code block.
+    ;; This is done by inserting a special rule before the normal
+    ;; indentation rules of these languages.
+    ;; The value of mhtml-ts-mode--js-css-indent-offset changes based on
+    ;; mhtml-ts-mode-tag-relative-indent and can be used to indent
+    ;; JavaScript and CSS code relative to the HTML that contains them,
+    ;; just like in mhtml-mode.
+    (setq-local treesit-simple-indent-rules
+                (append html-ts-mode--indent-rules
+                        ;; Extended rules for js and css, to
+                        ;; indent appropriately when injected
+                        ;; into html
+                        `((javascript ((parent-is "program")
+                                       mhtml-ts-mode--js-css-tag-bol
+                                       mhtml-ts-mode--js-css-indent-offset)
+                                      ,@(cdr (car js--treesit-indent-rules=
))))
+                        `((css ((parent-is "stylesheet")
+                                mhtml-ts-mode--js-css-tag-bol
+                                mhtml-ts-mode--js-css-indent-offset)
+                               ,@(cdr (car css--treesit-indent-rules))))))
+    ;; Navigation.
+
+    ;; This regular expression tells treesit how to match the node type
+    ;; of defun nodes.
+    ;; Used by `treesit-beginning-of-defun' and friends for
+    ;; navigations.
+    (setq-local treesit-defun-type-regexp
+                (rx (or
+                     ;; Javascript
+                     "class_declaration"
+                     "method_definition"
+                     "function_declaration"
+                     "lexical_declaration"
+                     ;; HTML
+                     "element"
+                     ;; CSS
+                     "rule_set")))
+
+    ;; This is for finding defun name, it's used by IMenu as default
+    ;; function no specific functions are defined.
+    (setq-local treesit-defun-name-function #'mhtml-ts-mode--defun-name)
+
+    ;; Define what are 'thing' for treesit.
+    ;; 'Thing' is a symbol representing the thing, like `defun', `sexp', or
+    ;; `sentence'.
+    (setq-local treesit-thing-settings
+                `((html
+                   (sexp ,(regexp-opt '("element"
+                                        "text"
+                                        "attribute"
+                                        "value")))
+                   (sentence "tag")
+                   (text ,(regexp-opt '("comment" "text"))))
+                  (javascript
+                   (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
+                   (sentence ,(js--regexp-opt-symbol js--treesit-sentence-=
nodes))
+                   (text ,(js--regexp-opt-symbol '("comment"
+                                                   "string_fragment"))))))
+
+    ;; Font-lock.
+
+    ;; In a multi-language scenario, font lock settings are usually a
+    ;; concatenation of language rules. As you can see, it is possible
+    ;; to extend/modify the default rule or use a different set of
+    ;; rules. See `php-ts-mode--custom-html-font-lock-settings' for more
+    ;; advanced usage.
+    (setq-local treesit-font-lock-settings
+                (append html-ts-mode--font-lock-settings
+                        js--treesit-font-lock-settings
+                        (append
+                         ;; Rule for coloring CSS property values.
+                         ;; Placed before `css--treesit-settings'
+                         ;; to win against the same rule contained therein.
+                         (treesit-font-lock-rules
+                          :language 'css
+                          :override t
+                          :feature 'variable
+                          '((plain_value) @mhtml-ts-mode--colorize-css-val=
ue))
+                         css--treesit-settings)))
+
+    ;; Tells treesit the list of features to fontify.
+    (setq-local treesit-font-lock-feature-list mhtml-ts-mode--feature-list)
+
+    ;; Imenu
+
+    ;; Setup Imenu: if no function is specified, try to find an object
+    ;; using `treesit-defun-name-function'.
+    ;; TODO: we need to see if it is possible to extend Imenu to
+    ;; embedded languages =E2=80=8B=E2=80=8Bas well.
+    (setq-local treesit-simple-imenu-settings
+                `(("Element" "\\`tag_name\\'" nil nil)))
+
+    ;; This should be the last thing to do.
+    ;; Treesit tries to find out what the primary language is, but it is b=
etter
+    ;; to say it explicitly.
+    (setq-local treesit-primary-parser (treesit-parser-create 'html))
+
+    (treesit-font-lock-recompute-features)
+    (treesit-major-mode-setup)))
+
+(when (and (treesit-ready-p 'html) (treesit-ready-p 'javascript) (treesit-=
ready-p 'css))
+  (add-to-list
+   'auto-mode-alist '("\\.[sx]?html?\\(\\.[a-zA-Z_]+\\)?\\'" . mhtml-ts-mo=
de)))
+
+(provide 'mhtml-ts-mode)
+;;; mhtml-ts-mode.el ends here
=2D-=20
2.47.1


--nextPart7111894.9J7NaK4W3v--







Acknowledgement sent to Vincenzo Pupillo <v.pupillo@HIDDEN>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs@HIDDEN. Full text available.
Report forwarded to bug-gnu-emacs@HIDDEN:
bug#74610; Package emacs. Full text available.
Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.
Last modified: Mon, 17 Feb 2025 07:30:03 UTC

GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997 nCipher Corporation Ltd, 1994-97 Ian Jackson.