Compare commits
4 Commits
54865be813
...
17efaa7880
Author | SHA1 | Date |
---|---|---|
Marcel Schramm | 17efaa7880 | |
Marcel Schramm | a517dc2cb3 | |
Marcel Schramm | 4e8f514a24 | |
Marcel Schramm | 8bdb018434 |
|
@ -1,20 +0,0 @@
|
|||
|
||||
version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.13
|
||||
|
||||
working_directory: /go/src/github.com/Bios-Marcel/cordless
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- run: go get -v -t -d ./...
|
||||
- run: go vet ./...
|
||||
- run: go test -race -coverprofile=profile.out -covermode=atomic ./...
|
||||
- run: bash <(curl -s https://codecov.io/bash) -f profile.out
|
||||
- run: go build
|
||||
|
||||
- store_artifacts:
|
||||
path: /go/src/github.com/Bios-Marcel/cordless/cordless
|
||||
destination: cordless
|
|
@ -1,5 +0,0 @@
|
|||
os: osx
|
||||
language: go
|
||||
go:
|
||||
- "1.13"
|
||||
script: go test -race ./...
|
22
README.md
22
README.md
|
@ -1,27 +1,13 @@
|
|||
# I AM CLOSING DOWN THE CORDLESS PROJECT
|
||||
|
||||
Hey, so I know this is somewhat of a bummer, but I got banned because of ToS violation today. This seemed to be connected to creating a new PM channel via the `/users/@me` endpoint. As that's basically a confirmation for what we've believed would never be enforced, I decided to not work on the cordless project anymore. I'll be taking down cordless in package managers in hope that no new users will install it anymore without knowing the risks. I believe that if you manage to build it yourself, you've probably read the README and are aware of the risks.
|
||||
I'll keep the repository up, but might archive it at some point. **And yes, you'll still be able to use existing binaries for as long as discord doesn't introduce any more breaking changes. However, be aware that the risk of getting a ban will only get higher with time!**
|
||||
I'll keep the repository up, but it'll be archived (read-only) and I have vendored the dependencies, meaning that you'll probably always be able to build the project from source as long as you have a compatible go compiler. **And yes, you'll still be able to use existing binaries for as long as discord doesn't introduce any more breaking changes. However, be aware that the risk of getting a ban will only get higher with time!**
|
||||
|
||||
<h1 align="center">Cordless</h1>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://circleci.com/gh/Bios-Marcel/cordless">
|
||||
<img src="https://img.shields.io/circleci/build/gh/Bios-Marcel/cordless?label=linux&logo=linux&logoColor=white">
|
||||
</a>
|
||||
<a href="https://travis-ci.org/Bios-Marcel/cordless">
|
||||
<img src="https://img.shields.io/travis/Bios-Marcel/cordless?label=darwin&logo=apple&logoColor=white">
|
||||
</a>
|
||||
<a href="https://ci.appveyor.com/project/Bios-Marcel/cordless/branch/master">
|
||||
<img src=https://img.shields.io/appveyor/ci/Bios-Marcel/cordless?label=windows&logo=windows&logoColor=white">
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/Bios-Marcel/cordless">
|
||||
<img src="https://codecov.io/gh/Bios-Marcel/cordless/branch/master/graph/badge.svg">
|
||||
</a>
|
||||
<a href="https://discord.gg/fxFqszu">
|
||||
<img src="https://img.shields.io/discord/600329866558308373.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2">
|
||||
</a>
|
||||
</p>
|
||||
The discord server still exists and there's still some people talking, so feel free to check it out if you want to:
|
||||
|
||||
https://discord.gg/fxFqszu
|
||||
|
||||
## Overview
|
||||
|
||||
|
|
19
appveyor.yml
19
appveyor.yml
|
@ -1,19 +0,0 @@
|
|||
clone_folder: c:\gopath\src\github.com\Bios-Marcel\cordless
|
||||
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
GO111MODULE: on
|
||||
build: off
|
||||
stack: go 1.13
|
||||
|
||||
artifacts:
|
||||
- path: cordless.exe
|
||||
name: cordless.exe
|
||||
|
||||
build_script:
|
||||
- go get -v -d ./...
|
||||
- go build -o cordless.exe
|
||||
|
||||
test_script:
|
||||
- go vet ./...
|
||||
- go test ./...
|
8
go.sum
8
go.sum
|
@ -54,11 +54,9 @@ github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tW
|
|||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
|
@ -68,7 +66,6 @@ github.com/mdp/qrterminal/v3 v3.0.0 h1:ywQqLRBXWTktytQNDKFjhAvoGkLVN3J2tAFZ0kMd9
|
|||
github.com/mdp/qrterminal/v3 v3.0.0/go.mod h1:NJpfAs7OAm77Dy8EkWrtE4aq+cE6McoLXlBqXQEwvE0=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
|
@ -78,16 +75,13 @@ github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
|||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac h1:kYPjbEN6YPYWWHI6ky1J813KzIq/8+Wg4TO4xU7A/KU=
|
||||
github.com/robertkrimen/otto v0.0.0-20200922221731-ef014fd054ac/go.mod h1:xvqspoSXJTIpemEonrMDFq6XzwHYYgToXWj5eRX1OtY=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
|
@ -102,7 +96,6 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG
|
|||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756 h1:9nuHUbU8dRnRRfj9KjWUVrJeoexdbeMjttk6Oh1rD10=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -112,7 +105,6 @@ golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2019, Marcel Schramm
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,27 @@
|
|||
# Discord Emoji Map
|
||||
|
||||
[![builds.sr.ht status](https://builds.sr.ht/~biosmarcel/discordemojimap/arch.yml.svg)](https://builds.sr.ht/~biosmarcel/discordemojimap/arch.yml?)
|
||||
[![GoDoc](https://godoc.org/github.com/Bios-Marcel/discordemojimap?status.svg)](https://godoc.org/github.com/Bios-Marcel/discordemojimap)
|
||||
[![codecov](https://codecov.io/gh/Bios-Marcel/discordemojimap/branch/master/graph/badge.svg)](https://codecov.io/gh/Bios-Marcel/discordemojimap)
|
||||
|
||||
This is the map of emojis that discord uses. However, I have left out
|
||||
different skin tones and such. A complete map might follow at some
|
||||
point.
|
||||
|
||||
## Usage
|
||||
|
||||
The usage is quite simple, you just pass your input string and it replaces all
|
||||
valid emoji sequences with their respective emojis.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Bios-Marcel/discordemojimap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println(discordemojimap.Replace("What a wonderful day :sun_with_face:, am I right?"))
|
||||
}
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/Bios-Marcel/discordemojimap
|
||||
|
||||
go 1.12
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,60 @@
|
|||
package discordemojimap
|
||||
|
||||
import "strings"
|
||||
|
||||
// ContainsEmoji returns true if that emoji is mapped to one or more key.
|
||||
func ContainsEmoji(emoji string) bool {
|
||||
for _, emojiInMap := range EmojiMap {
|
||||
if emojiInMap == emoji {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ContainsCode returns true if that emojicode is mapped to an emoji.
|
||||
func ContainsCode(emojiCode string) bool {
|
||||
_, contains := EmojiMap[emojiCode]
|
||||
return contains
|
||||
}
|
||||
|
||||
// GetEmojiCodes contains all codes for an emoji in an array. If no code could
|
||||
// be found, then the resulting array will be empty.
|
||||
func GetEmojiCodes(emoji string) []string {
|
||||
codes := make([]string, 0)
|
||||
for code, emojiInMap := range EmojiMap {
|
||||
if emojiInMap == emoji {
|
||||
codes = append(codes, code)
|
||||
}
|
||||
}
|
||||
|
||||
return codes
|
||||
}
|
||||
|
||||
// GetEmoji returns the matching emoji or an empty string in case no match was
|
||||
// found for the given code.
|
||||
func GetEmoji(emojiCode string) string {
|
||||
emoji, _ := EmojiMap[emojiCode]
|
||||
return emoji
|
||||
}
|
||||
|
||||
// GetEntriesStartingWith returns all key-value pairs where the key(code)
|
||||
// is prefixed with the given string. If no matches were found, the map is
|
||||
// empty.
|
||||
func GetEntriesStartingWith(startsWith string) map[string]string {
|
||||
matches := make(map[string]string)
|
||||
if len(startsWith) == 0 {
|
||||
return matches
|
||||
}
|
||||
|
||||
searchTerm := strings.TrimSuffix(startsWith, ":")
|
||||
|
||||
for emojiCode, emoji := range EmojiMap {
|
||||
if strings.HasPrefix(emojiCode, searchTerm) {
|
||||
matches[emojiCode] = emoji
|
||||
}
|
||||
}
|
||||
|
||||
return matches
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
// Package discordemojimap provides a Replace function in order to escape
|
||||
// emoji sequences with their respective emojis.
|
||||
package discordemojimap
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var emojiCodeRegex = regexp.MustCompile("(?s):[a-zA-Z0-9_]+:")
|
||||
|
||||
// Replace all emoji sequences contained in the discord emoji map with their
|
||||
// respective emojis.
|
||||
//
|
||||
// Examples for valid input:
|
||||
// Replace("Hello World :sun_with_face:")
|
||||
// would result in
|
||||
// "Hello World 🌞"
|
||||
func Replace(input string) string {
|
||||
if len(input) <= 2 {
|
||||
return input
|
||||
}
|
||||
|
||||
replacedEmojis := emojiCodeRegex.ReplaceAllStringFunc(input, func(match string) string {
|
||||
emojified, contains := EmojiMap[strings.ToLower(match[1:len(match)-1])]
|
||||
if !contains {
|
||||
return match
|
||||
}
|
||||
|
||||
return emojified
|
||||
})
|
||||
|
||||
return replacedEmojis
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
# IDE-specific metadata
|
||||
.idea/
|
|
@ -0,0 +1,28 @@
|
|||
Copyright (c) 2015, Bruce Marriner
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of discordgo nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Non-TOS compliant fork of Discordgo
|
||||
|
||||
This fork allows usage of normal discord accounts. The support sin't really
|
||||
proper and API versions might differ from endpoint to endpoint.
|
||||
|
||||
Examples are probably out of date, but it's fine by me, this is mostly for
|
||||
personal use. However, feel free to do with it what you will.
|
|
@ -0,0 +1,150 @@
|
|||
// Discordgo - Discord bindings for Go
|
||||
// Available at https://github.com/Bios-Marcel/discordgo
|
||||
|
||||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains high level helper functions and easy entry points for the
|
||||
// entire discordgo package. These functions are being developed and are very
|
||||
// experimental at this point. They will most likely change so please use the
|
||||
// low level functions if that's a problem.
|
||||
|
||||
// Package discordgo provides Discord binding for Go
|
||||
package discordgo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrMFA will be risen by New when the user has 2FA.
|
||||
var ErrMFA = errors.New("account has 2FA enabled")
|
||||
|
||||
const fakeBrowserUserAgent = "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:82.0) Gecko/20100101 Firefox/82.0"
|
||||
const botUserAgent = "Discord Bot (Bios-Marcel/discordgo)"
|
||||
|
||||
// NewWithToken creates a new Discord session and will use the given token
|
||||
// for authorization.
|
||||
func NewWithToken(token string) (s *Session, err error) {
|
||||
s = createEmptySession()
|
||||
//Make sure there's no unnecessary spaces / newlines from pasting.
|
||||
token = strings.TrimSpace(token)
|
||||
if strings.HasPrefix(strings.ToLower(token), "bot") {
|
||||
//Cut off "bot", ignoring casing and make sure it's "Bot "
|
||||
token = "Bot " + strings.TrimSpace(string([]rune(token)[3:]))
|
||||
s.Identify.Intents = MakeIntent(IntentsAllWithoutPrivileged)
|
||||
s.UserAgent = botUserAgent
|
||||
} else {
|
||||
s.MFA = strings.HasPrefix(token, "mfa")
|
||||
s.UserAgent = fakeBrowserUserAgent
|
||||
}
|
||||
|
||||
s.Token = token
|
||||
s.Identify.Token = token
|
||||
|
||||
// The Session is now able to have RestAPI methods called on it.
|
||||
// It is recommended that you now call Open() so that events will trigger.
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func createEmptySession() *Session {
|
||||
session := &Session{
|
||||
State: NewState(),
|
||||
Ratelimiter: NewRatelimiter(),
|
||||
StateEnabled: true,
|
||||
Compress: true,
|
||||
ShouldReconnectOnError: true,
|
||||
ShardID: 0,
|
||||
ShardCount: 1,
|
||||
MaxRestRetries: 3,
|
||||
Client: &http.Client{Timeout: (20 * time.Second)},
|
||||
sequence: new(int64),
|
||||
LastHeartbeatAck: time.Now().UTC(),
|
||||
}
|
||||
|
||||
// These can be modified prior to calling Open()
|
||||
// Initilize the Identify Package with defaults
|
||||
session.Identify.Compress = true
|
||||
session.Identify.LargeThreshold = 250
|
||||
session.Identify.GuildSubscriptions = true
|
||||
session.Identify.Properties.OS = runtime.GOOS
|
||||
session.Identify.Properties.Browser = "Firefox"
|
||||
session.Identify.Intents = MakeIntent(IntentsAll)
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
// NewWithPassword creates a new Discord session and will sign in with the
|
||||
// provided credentials.
|
||||
//
|
||||
// NOTE: While email/pass authentication is supported by DiscordGo it is
|
||||
// HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token
|
||||
// and then use that authentication token for all future connections.
|
||||
// Also, doing any form of automation with a user (non Bot) account may result
|
||||
// in that account being permanently banned from Discord.
|
||||
func NewWithPassword(username, password string) (s *Session, err error) {
|
||||
s = createEmptySession()
|
||||
s.UserAgent = fakeBrowserUserAgent
|
||||
_, err = s.Login(username, password)
|
||||
if err != nil || s.Token == "" {
|
||||
if s.MFA {
|
||||
err = ErrMFA
|
||||
} else {
|
||||
err = fmt.Errorf("Unable to fetch discord authentication token. %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// The Session is now able to have RestAPI methods called on it.
|
||||
// It is recommended that you now call Open() so that events will trigger.
|
||||
|
||||
// NewWithPasswordAndMFA that also takes a MFA token generated by an authenticator.
|
||||
//
|
||||
// NOTE: While email/pass authentication is supported by DiscordGo it is
|
||||
// HIGHLY DISCOURAGED by Discord. Please only use email/pass to obtain a token
|
||||
// and then use that authentication token for all future connections.
|
||||
// Also, doing any form of automation with a user (non Bot) account may result
|
||||
// in that account being permanently banned from Discord.
|
||||
func NewWithPasswordAndMFA(username, password, mfaToken string) (s *Session, err error) {
|
||||
s = createEmptySession()
|
||||
s.UserAgent = fakeBrowserUserAgent
|
||||
var loginInfo *LoginInfo
|
||||
loginInfo, err = s.Login(username, password)
|
||||
if err != nil || s.Token == "" {
|
||||
if s.MFA {
|
||||
if mfaToken == "" {
|
||||
err = ErrMFA
|
||||
} else {
|
||||
if loginInfo == nil {
|
||||
err = ErrMFA
|
||||
return
|
||||
}
|
||||
|
||||
var token string
|
||||
token, err = s.totp(loginInfo.Ticket, mfaToken)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.Token = token
|
||||
s.Identify.Token = token
|
||||
}
|
||||
} else {
|
||||
err = fmt.Errorf("Unable to fetch discord authentication token. %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// The Session is now able to have RestAPI methods called on it.
|
||||
// It is recommended that you now call Open() so that events will trigger.
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
// Discordgo - Discord bindings for Go
|
||||
// Available at https://github.com/Bios-Marcel/discordgo
|
||||
|
||||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains variables for all known Discord end points. All functions
|
||||
// throughout the Discordgo package use these variables for all connections
|
||||
// to Discord. These are all exported and you may modify them if needed.
|
||||
|
||||
package discordgo
|
||||
|
||||
import "strconv"
|
||||
|
||||
// APIVersion is the Discord API version used for the REST and Websocket API.
|
||||
var APIVersion = "6"
|
||||
|
||||
// Known Discord API Endpoints.
|
||||
var (
|
||||
EndpointStatus string
|
||||
EndpointSm string
|
||||
EndpointSmActive string
|
||||
EndpointSmUpcoming string
|
||||
|
||||
EndpointDiscord string
|
||||
EndpointAPINoVersion string
|
||||
EndpointAPI string
|
||||
EndpointGuilds string
|
||||
EndpointChannels string
|
||||
EndpointUsers string
|
||||
EndpointGateway string
|
||||
EndpointGatewayBot string
|
||||
EndpointWebhooks string
|
||||
EndpointReadStates string
|
||||
|
||||
EndpointReadStatesAckBulk string
|
||||
|
||||
EndpointCDN string
|
||||
EndpointCDNAttachments string
|
||||
EndpointCDNAvatars string
|
||||
EndpointCDNIcons string
|
||||
EndpointCDNSplashes string
|
||||
EndpointCDNChannelIcons string
|
||||
EndpointCDNBanners string
|
||||
|
||||
EndpointAuth string
|
||||
EndpointLogin string
|
||||
EndpointTotpLogin string
|
||||
EndpointUserMFA string
|
||||
EndpointMFACodes string
|
||||
EndpointTotpEnable string
|
||||
EndpointTotpDisable string
|
||||
EndpointLogout string
|
||||
EndpointVerify string
|
||||
EndpointVerifyResend string
|
||||
EndpointForgotPassword string
|
||||
EndpointResetPassword string
|
||||
EndpointRegister string
|
||||
|
||||
EndpointVoice string
|
||||
EndpointVoiceRegions string
|
||||
EndpointVoiceIce string
|
||||
|
||||
EndpointTutorial string
|
||||
EndpointTutorialIndicators string
|
||||
|
||||
EndpointTrack string
|
||||
EndpointSso string
|
||||
EndpointReport string
|
||||
EndpointIntegrations string
|
||||
|
||||
EndpointUser = func(uID string) string { return EndpointUsers + uID }
|
||||
EndpointUserAvatar = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".png" }
|
||||
EndpointUserAvatarAnimated = func(uID, aID string) string { return EndpointCDNAvatars + uID + "/" + aID + ".gif" }
|
||||
EndpointDefaultUserAvatar = func(uDiscriminator string) string {
|
||||
uDiscriminatorInt, _ := strconv.Atoi(uDiscriminator)
|
||||
return EndpointCDN + "embed/avatars/" + strconv.Itoa(uDiscriminatorInt%5) + ".png"
|
||||
}
|
||||
EndpointUserSettings = func(uID string) string { return EndpointUsers + uID + "/settings" }
|
||||
EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" }
|
||||
EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID }
|
||||
EndpointUserGuildSettings = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID + "/settings" }
|
||||
EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" }
|
||||
EndpointUserChannelsV8 = func(uID string) string { return EndpointAPINoVersion + "8/users/" + uID + "/channels" }
|
||||
EndpointUserDevices = func(uID string) string { return EndpointUsers + uID + "/devices" }
|
||||
EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" }
|
||||
EndpointUserNotes = func(uID string) string { return EndpointUsers + "@me/notes/" + uID }
|
||||
|
||||
EndpointGuild = func(gID string) string { return EndpointGuilds + gID }
|
||||
EndpointGuildChannels = func(gID string) string { return EndpointGuilds + gID + "/channels" }
|
||||
EndpointGuildMembers = func(gID string) string { return EndpointGuilds + gID + "/members" }
|
||||
EndpointGuildMember = func(gID, uID string) string { return EndpointGuilds + gID + "/members/" + uID }
|
||||
EndpointGuildMemberRole = func(gID, uID, rID string) string { return EndpointGuilds + gID + "/members/" + uID + "/roles/" + rID }
|
||||
EndpointGuildBans = func(gID string) string { return EndpointGuilds + gID + "/bans" }
|
||||
EndpointGuildBan = func(gID, uID string) string { return EndpointGuilds + gID + "/bans/" + uID }
|
||||
EndpointGuildIntegrations = func(gID string) string { return EndpointGuilds + gID + "/integrations" }
|
||||
EndpointGuildIntegration = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID }
|
||||
EndpointGuildIntegrationSync = func(gID, iID string) string { return EndpointGuilds + gID + "/integrations/" + iID + "/sync" }
|
||||
EndpointGuildRoles = func(gID string) string { return EndpointGuilds + gID + "/roles" }
|
||||
EndpointGuildRole = func(gID, rID string) string { return EndpointGuilds + gID + "/roles/" + rID }
|
||||
EndpointGuildInvites = func(gID string) string { return EndpointGuilds + gID + "/invites" }
|
||||
EndpointGuildEmbed = func(gID string) string { return EndpointGuilds + gID + "/embed" }
|
||||
EndpointGuildPrune = func(gID string) string { return EndpointGuilds + gID + "/prune" }
|
||||
EndpointGuildIcon = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".png" }
|
||||
EndpointGuildIconAnimated = func(gID, hash string) string { return EndpointCDNIcons + gID + "/" + hash + ".gif" }
|
||||
EndpointGuildSplash = func(gID, hash string) string { return EndpointCDNSplashes + gID + "/" + hash + ".png" }
|
||||
EndpointGuildWebhooks = func(gID string) string { return EndpointGuilds + gID + "/webhooks" }
|
||||
EndpointGuildAuditLogs = func(gID string) string { return EndpointGuilds + gID + "/audit-logs" }
|
||||
EndpointGuildEmojis = func(gID string) string { return EndpointGuilds + gID + "/emojis" }
|
||||
EndpointGuildEmoji = func(gID, eID string) string { return EndpointGuilds + gID + "/emojis/" + eID }
|
||||
EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" }
|
||||
|
||||
EndpointChannel = func(cID string) string { return EndpointChannels + cID }
|
||||
EndpointChannelPermissions = func(cID string) string { return EndpointChannels + cID + "/permissions" }
|
||||
EndpointChannelPermission = func(cID, tID string) string { return EndpointChannels + cID + "/permissions/" + tID }
|
||||
EndpointChannelInvites = func(cID string) string { return EndpointChannels + cID + "/invites" }
|
||||
EndpointChannelTyping = func(cID string) string { return EndpointChannels + cID + "/typing" }
|
||||
EndpointChannelMessages = func(cID string) string { return EndpointChannels + cID + "/messages" }
|
||||
EndpointChannelMessage = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID }
|
||||
EndpointChannelMessageAck = func(cID, mID string) string { return EndpointChannels + cID + "/messages/" + mID + "/ack" }
|
||||
EndpointChannelMessagesBulkDelete = func(cID string) string { return EndpointChannel(cID) + "/messages/bulk-delete" }
|
||||
EndpointChannelMessagesPins = func(cID string) string { return EndpointChannel(cID) + "/pins" }
|
||||
EndpointChannelMessagePin = func(cID, mID string) string { return EndpointChannel(cID) + "/pins/" + mID }
|
||||
|
||||
EndpointGroupIcon = func(cID, hash string) string { return EndpointCDNChannelIcons + cID + "/" + hash + ".png" }
|
||||
|
||||
EndpointChannelWebhooks = func(cID string) string { return EndpointChannel(cID) + "/webhooks" }
|
||||
EndpointWebhook = func(wID string) string { return EndpointWebhooks + wID }
|
||||
EndpointWebhookToken = func(wID, token string) string { return EndpointWebhooks + wID + "/" + token }
|
||||
|
||||
EndpointMessageReactionsAll = func(cID, mID string) string {
|
||||
return EndpointChannelMessage(cID, mID) + "/reactions"
|
||||
}
|
||||
EndpointMessageReactions = func(cID, mID, eID string) string {
|
||||
return EndpointChannelMessage(cID, mID) + "/reactions/" + eID
|
||||
}
|
||||
EndpointMessageReaction = func(cID, mID, eID, uID string) string {
|
||||
return EndpointMessageReactions(cID, mID, eID) + "/" + uID
|
||||
}
|
||||
|
||||
EndpointRelationships = func() string { return EndpointUsers + "@me" + "/relationships" }
|
||||
EndpointRelationship = func(uID string) string { return EndpointRelationships() + "/" + uID }
|
||||
EndpointRelationshipsMutual = func(uID string) string { return EndpointUsers + uID + "/relationships" }
|
||||
|
||||
EndpointGuildCreate string
|
||||
|
||||
EndpointInvite = func(iID string) string { return EndpointAPI + "invite/" + iID }
|
||||
|
||||
EndpointIntegrationsJoin = func(iID string) string { return EndpointAPI + "integrations/" + iID + "/join" }
|
||||
|
||||
EndpointEmoji = func(eID string) string { return EndpointAPI + "emojis/" + eID + ".png" }
|
||||
EndpointEmojiAnimated = func(eID string) string { return EndpointAPI + "emojis/" + eID + ".gif" }
|
||||
|
||||
EndpointOauth2 = EndpointAPI + "oauth2/"
|
||||
EndpointApplications = EndpointOauth2 + "applications"
|
||||
EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID }
|
||||
EndpointApplicationsBot = func(aID string) string { return EndpointApplications + "/" + aID + "/bot" }
|
||||
EndpointApplicationAssets = func(aID string) string { return EndpointApplications + "/" + aID + "/assets" }
|
||||
)
|
||||
|
||||
func init() {
|
||||
SetEndpoints("https://discord.com/", "https://status.discord.com/api/v2/", "https://cdn.discordapp.com/")
|
||||
}
|
||||
|
||||
// SetEndpoints sets the endpoints for the status, cdn and main communication.
|
||||
func SetEndpoints(main, status, cdn string) {
|
||||
EndpointStatus = status
|
||||
EndpointSm = EndpointStatus + "scheduled-maintenances/"
|
||||
EndpointSmActive = EndpointSm + "active.json"
|
||||
EndpointSmUpcoming = EndpointSm + "upcoming.json"
|
||||
|
||||
EndpointDiscord = main
|
||||
EndpointAPINoVersion = EndpointDiscord + "api/v"
|
||||
EndpointAPI = EndpointDiscord + "api/v" + APIVersion + "/"
|
||||
EndpointGuilds = EndpointAPI + "guilds/"
|
||||
EndpointChannels = EndpointAPI + "channels/"
|
||||
EndpointUsers = EndpointAPI + "users/"
|
||||
EndpointGateway = EndpointAPI + "gateway"
|
||||
EndpointGatewayBot = EndpointGateway + "/bot"
|
||||
EndpointWebhooks = EndpointAPI + "webhooks/"
|
||||
EndpointReadStates = EndpointAPINoVersion + "8/read-states/"
|
||||
|
||||
EndpointReadStatesAckBulk = EndpointReadStates + "ack-bulk"
|
||||
|
||||
EndpointCDN = cdn
|
||||
EndpointCDNAttachments = EndpointCDN + "attachments/"
|
||||
EndpointCDNAvatars = EndpointCDN + "avatars/"
|
||||
EndpointCDNIcons = EndpointCDN + "icons/"
|
||||
EndpointCDNSplashes = EndpointCDN + "splashes/"
|
||||
EndpointCDNChannelIcons = EndpointCDN + "channel-icons/"
|
||||
EndpointCDNBanners = EndpointCDN + "banners/"
|
||||
|
||||
EndpointAuth = EndpointAPI + "auth/"
|
||||
EndpointLogin = EndpointAuth + "login"
|
||||
EndpointTotpLogin = EndpointAuth + "mfa/totp"
|
||||
EndpointUserMFA = EndpointUsers + "@me/mfa/"
|
||||
EndpointMFACodes = EndpointUserMFA + "codes"
|
||||
EndpointTotpEnable = EndpointUserMFA + "totp/enable"
|
||||
EndpointTotpDisable = EndpointUserMFA + "totp/disable"
|
||||
EndpointLogout = EndpointAuth + "logout"
|
||||
EndpointVerify = EndpointAuth + "verify"
|
||||
EndpointVerifyResend = EndpointAuth + "verify/resend"
|
||||
EndpointForgotPassword = EndpointAuth + "forgot"
|
||||
EndpointResetPassword = EndpointAuth + "reset"
|
||||
EndpointRegister = EndpointAuth + "register"
|
||||
|
||||
EndpointVoice = EndpointAPI + "/voice/"
|
||||
EndpointVoiceRegions = EndpointVoice + "regions"
|
||||
EndpointVoiceIce = EndpointVoice + "ice"
|
||||
|
||||
EndpointTutorial = EndpointAPI + "tutorial/"
|
||||
EndpointTutorialIndicators = EndpointTutorial + "indicators"
|
||||
|
||||
EndpointTrack = EndpointAPI + "track"
|
||||
EndpointSso = EndpointAPI + "sso"
|
||||
EndpointReport = EndpointAPI + "report"
|
||||
EndpointIntegrations = EndpointAPI + "integrations"
|
||||
|
||||
EndpointGuildCreate = EndpointAPI + "guilds"
|
||||
}
|
|
@ -0,0 +1,249 @@
|
|||
package discordgo
|
||||
|
||||
// EventHandler is an interface for Discord events.
|
||||
type EventHandler interface {
|
||||
// Type returns the type of event this handler belongs to.
|
||||
Type() string
|
||||
|
||||
// Handle is called whenever an event of Type() happens.
|
||||
// It is the receivers responsibility to type assert that the interface
|
||||
// is the expected struct.
|
||||
Handle(*Session, interface{})
|
||||
}
|
||||
|
||||
// EventInterfaceProvider is an interface for providing empty interfaces for
|
||||
// Discord events.
|
||||
type EventInterfaceProvider interface {
|
||||
// Type is the type of event this handler belongs to.
|
||||
Type() string
|
||||
|
||||
// New returns a new instance of the struct this event handler handles.
|
||||
// This is called once per event.
|
||||
// The struct is provided to all handlers of the same Type().
|
||||
New() interface{}
|
||||
}
|
||||
|
||||
// interfaceEventType is the event handler type for interface{} events.
|
||||
const interfaceEventType = "__INTERFACE__"
|
||||
|
||||
// interfaceEventHandler is an event handler for interface{} events.
|
||||
type interfaceEventHandler func(*Session, interface{})
|
||||
|
||||
// Type returns the event type for interface{} events.
|
||||
func (eh interfaceEventHandler) Type() string {
|
||||
return interfaceEventType
|
||||
}
|
||||
|
||||
// Handle is the handler for an interface{} event.
|
||||
func (eh interfaceEventHandler) Handle(s *Session, i interface{}) {
|
||||
eh(s, i)
|
||||
}
|
||||
|
||||
var registeredInterfaceProviders = map[string]EventInterfaceProvider{}
|
||||
|
||||
// registerInterfaceProvider registers a provider so that DiscordGo can
|
||||
// access it's New() method.
|
||||
func registerInterfaceProvider(eh EventInterfaceProvider) {
|
||||
if _, ok := registeredInterfaceProviders[eh.Type()]; ok {
|
||||
return
|
||||
// XXX:
|
||||
// if we should error here, we need to do something with it.
|
||||
// fmt.Errorf("event %s already registered", eh.Type())
|
||||
}
|
||||
registeredInterfaceProviders[eh.Type()] = eh
|
||||
return
|
||||
}
|
||||
|
||||
// eventHandlerInstance is a wrapper around an event handler, as functions
|
||||
// cannot be compared directly.
|
||||
type eventHandlerInstance struct {
|
||||
eventHandler EventHandler
|
||||
}
|
||||
|
||||
// addEventHandler adds an event handler that will be fired anytime
|
||||
// the Discord WSAPI matching eventHandler.Type() fires.
|
||||
func (s *Session) addEventHandler(eventHandler EventHandler) func() {
|
||||
s.handlersMu.Lock()
|
||||
defer s.handlersMu.Unlock()
|
||||
|
||||
if s.handlers == nil {
|
||||
s.handlers = map[string][]*eventHandlerInstance{}
|
||||
}
|
||||
|
||||
ehi := &eventHandlerInstance{eventHandler}
|
||||
s.handlers[eventHandler.Type()] = append(s.handlers[eventHandler.Type()], ehi)
|
||||
|
||||
return func() {
|
||||
s.removeEventHandlerInstance(eventHandler.Type(), ehi)
|
||||
}
|
||||
}
|
||||
|
||||
// addEventHandler adds an event handler that will be fired the next time
|
||||
// the Discord WSAPI matching eventHandler.Type() fires.
|
||||
func (s *Session) addEventHandlerOnce(eventHandler EventHandler) func() {
|
||||
s.handlersMu.Lock()
|
||||
defer s.handlersMu.Unlock()
|
||||
|
||||
if s.onceHandlers == nil {
|
||||
s.onceHandlers = map[string][]*eventHandlerInstance{}
|
||||
}
|
||||
|
||||
ehi := &eventHandlerInstance{eventHandler}
|
||||
s.onceHandlers[eventHandler.Type()] = append(s.onceHandlers[eventHandler.Type()], ehi)
|
||||
|
||||
return func() {
|
||||
s.removeEventHandlerInstance(eventHandler.Type(), ehi)
|
||||
}
|
||||
}
|
||||
|
||||
// AddHandler allows you to add an event handler that will be fired anytime
|
||||
// the Discord WSAPI event that matches the function fires.
|
||||
// The first parameter is a *Session, and the second parameter is a pointer
|
||||
// to a struct corresponding to the event for which you want to listen.
|
||||
//
|
||||
// eg:
|
||||
// Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
|
||||
// })
|
||||
//
|
||||
// or:
|
||||
// Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) {
|
||||
// })
|
||||
//
|
||||
// List of events can be found at this page, with corresponding names in the
|
||||
// library for each event: https://discord.com/developers/docs/topics/gateway#event-names
|
||||
// There are also synthetic events fired by the library internally which are
|
||||
// available for handling, like Connect, Disconnect, and RateLimit.
|
||||
// events.go contains all of the Discord WSAPI and synthetic events that can be handled.
|
||||
//
|
||||
// The return value of this method is a function, that when called will remove the
|
||||
// event handler.
|
||||
func (s *Session) AddHandler(handler interface{}) func() {
|
||||
eh := handlerForInterface(handler)
|
||||
|
||||
if eh == nil {
|
||||
s.log(LogError, "Invalid handler type, handler will never be called")
|
||||
return func() {}
|
||||
}
|
||||
|
||||
return s.addEventHandler(eh)
|
||||
}
|
||||
|
||||
// AddHandlerOnce allows you to add an event handler that will be fired the next time
|
||||
// the Discord WSAPI event that matches the function fires.
|
||||
// See AddHandler for more details.
|
||||
func (s *Session) AddHandlerOnce(handler interface{}) func() {
|
||||
eh := handlerForInterface(handler)
|
||||
|
||||
if eh == nil {
|
||||
s.log(LogError, "Invalid handler type, handler will never be called")
|
||||
return func() {}
|
||||
}
|
||||
|
||||
return s.addEventHandlerOnce(eh)
|
||||
}
|
||||
|
||||
// removeEventHandler instance removes an event handler instance.
|
||||
func (s *Session) removeEventHandlerInstance(t string, ehi *eventHandlerInstance) {
|
||||
s.handlersMu.Lock()
|
||||
defer s.handlersMu.Unlock()
|
||||
|
||||
handlers := s.handlers[t]
|
||||
for i := range handlers {
|
||||
if handlers[i] == ehi {
|
||||
s.handlers[t] = append(handlers[:i], handlers[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
onceHandlers := s.onceHandlers[t]
|
||||
for i := range onceHandlers {
|
||||
if onceHandlers[i] == ehi {
|
||||
s.onceHandlers[t] = append(onceHandlers[:i], handlers[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handles calling permanent and once handlers for an event type.
|
||||
func (s *Session) handle(t string, i interface{}) {
|
||||
for _, eh := range s.handlers[t] {
|
||||
if s.SyncEvents {
|
||||
eh.eventHandler.Handle(s, i)
|
||||
} else {
|
||||
ehTemp := eh
|
||||
go ehTemp.eventHandler.Handle(s, i)
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.onceHandlers[t]) > 0 {
|
||||
for _, eh := range s.onceHandlers[t] {
|
||||
if s.SyncEvents {
|
||||
eh.eventHandler.Handle(s, i)
|
||||
} else {
|
||||
ehTemp := eh
|
||||
go ehTemp.eventHandler.Handle(s, i)
|
||||
}
|
||||
}
|
||||
s.onceHandlers[t] = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Handles an event type by calling internal methods, firing handlers and firing the
|
||||
// interface{} event.
|
||||
func (s *Session) handleEvent(t string, i interface{}) {
|
||||
s.handlersMu.RLock()
|
||||
defer s.handlersMu.RUnlock()
|
||||
|
||||
// All events are dispatched internally first.
|
||||
s.onInterface(i)
|
||||
|
||||
// Then they are dispatched to anyone handling interface{} events.
|
||||
s.handle(interfaceEventType, i)
|
||||
|
||||
// Finally they are dispatched to any typed handlers.
|
||||
s.handle(t, i)
|
||||
}
|
||||
|
||||
// setGuildIds will set the GuildID on all the members of a guild.
|
||||
// This is done as event data does not have it set.
|
||||
func setGuildIds(g *Guild) {
|
||||
for _, c := range g.Channels {
|
||||
c.GuildID = g.ID
|
||||
}
|
||||
|
||||
for _, m := range g.Members {
|
||||
m.GuildID = g.ID
|
||||
}
|
||||
|
||||
for _, vs := range g.VoiceStates {
|
||||
vs.GuildID = g.ID
|
||||
}
|
||||
}
|
||||
|
||||
// onInterface handles all internal events and routes them to the appropriate internal handler.
|
||||
func (s *Session) onInterface(i interface{}) {
|
||||
switch t := i.(type) {
|
||||
case *Ready:
|
||||
for _, g := range t.Guilds {
|
||||
setGuildIds(g)
|
||||
}
|
||||
s.onReady(t)
|
||||
case *GuildCreate:
|
||||
setGuildIds(t.Guild)
|
||||
case *GuildUpdate:
|
||||
setGuildIds(t.Guild)
|
||||
case *VoiceServerUpdate:
|
||||
go s.onVoiceServerUpdate(t)
|
||||
case *VoiceStateUpdate:
|
||||
go s.onVoiceStateUpdate(t)
|
||||
}
|
||||
err := s.State.OnInterface(s, i)
|
||||
if err != nil {
|
||||
s.log(LogDebug, "error dispatching internal event, %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// onReady handles the ready event.
|
||||
func (s *Session) onReady(r *Ready) {
|
||||
|
||||
// Store the SessionID within the Session struct.
|
||||
s.sessionID = r.SessionID
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,268 @@
|
|||
package discordgo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// This file contains all the possible structs that can be
|
||||
// handled by AddHandler/EventHandler.
|
||||
// DO NOT ADD ANYTHING BUT EVENT HANDLER STRUCTS TO THIS FILE.
|
||||
//go:generate go run tools/cmd/eventhandlers/main.go
|
||||
|
||||
// Connect is the data for a Connect event.
|
||||
// This is a synthetic event and is not dispatched by Discord.
|
||||
type Connect struct{}
|
||||
|
||||
// Disconnect is the data for a Disconnect event.
|
||||
// This is a synthetic event and is not dispatched by Discord.
|
||||
type Disconnect struct{}
|
||||
|
||||
// RateLimit is the data for a RateLimit event.
|
||||
// This is a synthetic event and is not dispatched by Discord.
|
||||
type RateLimit struct {
|
||||
*TooManyRequests
|
||||
URL string
|
||||
}
|
||||
|
||||
// Event provides a basic initial struct for all websocket events.
|
||||
type Event struct {
|
||||
Operation int `json:"op"`
|
||||
Sequence int64 `json:"s"`
|
||||
Type string `json:"t"`
|
||||
RawData json.RawMessage `json:"d"`
|
||||
// Struct contains one of the other types in this file.
|
||||
Struct interface{} `json:"-"`
|
||||
}
|
||||
|
||||
// A Ready stores all data for the websocket READY event.
|
||||
type Ready struct {
|
||||
Version int `json:"v"`
|
||||
SessionID string `json:"session_id"`
|
||||
User *User `json:"user"`
|
||||
ReadState []*ReadState `json:"read_state"`
|
||||
PrivateChannels []*Channel `json:"private_channels"`
|
||||
Guilds []*Guild `json:"guilds"`
|
||||
|
||||
// Undocumented fields
|
||||
Settings *Settings `json:"user_settings"`
|
||||
UserGuildSettings []*UserGuildSettings `json:"user_guild_settings"`
|
||||
Relationships []*Relationship `json:"relationships"`
|
||||
Presences []*Presence `json:"presences"`
|
||||
Notes map[string]string `json:"notes"`
|
||||
}
|
||||
|
||||
// ChannelCreate is the data for a ChannelCreate event.
|
||||
type ChannelCreate struct {
|
||||
*Channel
|
||||
}
|
||||
|
||||
// ChannelUpdate is the data for a ChannelUpdate event.
|
||||
type ChannelUpdate struct {
|
||||
*Channel
|
||||
}
|
||||
|
||||
// ChannelDelete is the data for a ChannelDelete event.
|
||||
type ChannelDelete struct {
|
||||
*Channel
|
||||
}
|
||||
|
||||
// ChannelPinsUpdate stores data for a ChannelPinsUpdate event.
|
||||
type ChannelPinsUpdate struct {
|
||||
LastPinTimestamp string `json:"last_pin_timestamp"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
GuildID string `json:"guild_id,omitempty"`
|
||||
}
|
||||
|
||||
// GuildCreate is the data for a GuildCreate event.
|
||||
type GuildCreate struct {
|
||||
*Guild
|
||||
}
|
||||
|
||||
// GuildUpdate is the data for a GuildUpdate event.
|
||||
type GuildUpdate struct {
|
||||
*Guild
|
||||
}
|
||||
|
||||
// GuildDelete is the data for a GuildDelete event.
|
||||
type GuildDelete struct {
|
||||
*Guild
|
||||
}
|
||||
|
||||
// GuildBanAdd is the data for a GuildBanAdd event.
|
||||
type GuildBanAdd struct {
|
||||
User *User `json:"user"`
|
||||
GuildID string `json:"guild_id"`
|
||||
}
|
||||
|
||||
// GuildBanRemove is the data for a GuildBanRemove event.
|
||||
type GuildBanRemove struct {
|
||||
User *User `json:"user"`
|
||||
GuildID string `json:"guild_id"`
|
||||
}
|
||||
|
||||
// GuildMemberAdd is the data for a GuildMemberAdd event.
|
||||
type GuildMemberAdd struct {
|
||||
*Member
|
||||
}
|
||||
|
||||
// GuildMemberUpdate is the data for a GuildMemberUpdate event.
|
||||
type GuildMemberUpdate struct {
|
||||
*Member
|
||||
}
|
||||
|
||||
// GuildMemberRemove is the data for a GuildMemberRemove event.
|
||||
type GuildMemberRemove struct {
|
||||
*Member
|
||||
}
|
||||
|
||||
// GuildRoleCreate is the data for a GuildRoleCreate event.
|
||||
type GuildRoleCreate struct {
|
||||
*GuildRole
|
||||
}
|
||||
|
||||
// GuildRoleUpdate is the data for a GuildRoleUpdate event.
|
||||
type GuildRoleUpdate struct {
|
||||
*GuildRole
|
||||
}
|
||||
|
||||
// A GuildRoleDelete is the data for a GuildRoleDelete event.
|
||||
type GuildRoleDelete struct {
|
||||
RoleID string `json:"role_id"`
|
||||
GuildID string `json:"guild_id"`
|
||||
}
|
||||
|
||||
// A GuildEmojisUpdate is the data for a guild emoji update event.
|
||||
type GuildEmojisUpdate struct {
|
||||
GuildID string `json:"guild_id"`
|
||||
Emojis []*Emoji `json:"emojis"`
|
||||
}
|
||||
|
||||
// A GuildMembersChunk is the data for a GuildMembersChunk event.
|
||||
type GuildMembersChunk struct {
|
||||
GuildID string `json:"guild_id"`
|
||||
Members []*Member `json:"members"`
|
||||
ChunkIndex int `json:"chunk_index"`
|
||||
ChunkCount int `json:"chunk_count"`
|
||||
Presences []*Presence `json:"presences,omitempty"`
|
||||
}
|
||||
|
||||
// GuildIntegrationsUpdate is the data for a GuildIntegrationsUpdate event.
|
||||
type GuildIntegrationsUpdate struct {
|
||||
GuildID string `json:"guild_id"`
|
||||
}
|
||||
|
||||
// MessageAck is the data for a MessageAck event.
|
||||
type MessageAck struct {
|
||||
MessageID string `json:"message_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
}
|
||||
|
||||
// MessageCreate is the data for a MessageCreate event.
|
||||
type MessageCreate struct {
|
||||
*Message
|
||||
}
|
||||
|
||||
// MessageUpdate is the data for a MessageUpdate event.
|
||||
type MessageUpdate struct {
|
||||
*Message
|
||||
// BeforeUpdate will be nil if the Message was not previously cached in the state cache.
|
||||
BeforeUpdate *Message `json:"-"`
|
||||
}
|
||||
|
||||
// MessageDelete is the data for a MessageDelete event.
|
||||
type MessageDelete struct {
|
||||
*Message
|
||||
BeforeDelete *Message `json:"-"`
|
||||
}
|
||||
|
||||
// MessageReactionAdd is the data for a MessageReactionAdd event.
|
||||
type MessageReactionAdd struct {
|
||||
*MessageReaction
|
||||
}
|
||||
|
||||
// MessageReactionRemove is the data for a MessageReactionRemove event.
|
||||
type MessageReactionRemove struct {
|
||||
*MessageReaction
|
||||
}
|
||||
|
||||
// MessageReactionRemoveAll is the data for a MessageReactionRemoveAll event.
|
||||
type MessageReactionRemoveAll struct {
|
||||
*MessageReaction
|
||||
}
|
||||
|
||||
// PresencesReplace is the data for a PresencesReplace event.
|
||||
type PresencesReplace []*Presence
|
||||
|
||||
// PresenceUpdate is the data for a PresenceUpdate event.
|
||||
type PresenceUpdate struct {
|
||||
Presence
|
||||
GuildID string `json:"guild_id"`
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
// Resumed is the data for a Resumed event.
|
||||
type Resumed struct {
|
||||
Trace []string `json:"_trace"`
|
||||
}
|
||||
|
||||
// RelationshipAdd is the data for a RelationshipAdd event.
|
||||
type RelationshipAdd struct {
|
||||
*Relationship
|
||||
}
|
||||
|
||||
// RelationshipRemove is the data for a RelationshipRemove event.
|
||||
type RelationshipRemove struct {
|
||||
*Relationship
|
||||
}
|
||||
|
||||
// TypingStart is the data for a TypingStart event.
|
||||
type TypingStart struct {
|
||||
UserID string `json:"user_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
GuildID string `json:"guild_id,omitempty"`
|
||||
Timestamp int `json:"timestamp"`
|
||||
}
|
||||
|
||||
// UserUpdate is the data for a UserUpdate event.
|
||||
type UserUpdate struct {
|
||||
*User
|
||||
}
|
||||
|
||||
// UserSettingsUpdate is the data for a UserSettingsUpdate event.
|
||||
type UserSettingsUpdate map[string]interface{}
|
||||
|
||||
// UserGuildSettingsUpdate is the data for a UserGuildSettingsUpdate event.
|
||||
type UserGuildSettingsUpdate struct {
|
||||
*UserGuildSettings
|
||||
}
|
||||
|
||||
// UserNoteUpdate is the data for a UserNoteUpdate event.
|
||||
type UserNoteUpdate struct {
|
||||
ID string `json:"id"`
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
||||
// VoiceServerUpdate is the data for a VoiceServerUpdate event.
|
||||
type VoiceServerUpdate struct {
|
||||
Token string `json:"token"`
|
||||
GuildID string `json:"guild_id"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
}
|
||||
|
||||
// VoiceStateUpdate is the data for a VoiceStateUpdate event.
|
||||
type VoiceStateUpdate struct {
|
||||
*VoiceState
|
||||
}
|
||||
|
||||
// MessageDeleteBulk is the data for a MessageDeleteBulk event
|
||||
type MessageDeleteBulk struct {
|
||||
Messages []string `json:"ids"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
GuildID string `json:"guild_id"`
|
||||
}
|
||||
|
||||
// WebhooksUpdate is the data for a WebhooksUpdate event
|
||||
type WebhooksUpdate struct {
|
||||
GuildID string `json:"guild_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
module github.com/Bios-Marcel/discordgo
|
||||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897
|
||||
)
|
|
@ -0,0 +1,10 @@
|
|||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
|
||||
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
@ -0,0 +1,103 @@
|
|||
// Discordgo - Discord bindings for Go
|
||||
// Available at https://github.com/Bios-Marcel/discordgo
|
||||
|
||||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains code related to discordgo package logging
|
||||
|
||||
package discordgo
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
// LogError level is used for critical errors that could lead to data loss
|
||||
// or panic that would not be returned to a calling function.
|
||||
LogError int = iota
|
||||
|
||||
// LogWarning level is used for very abnormal events and errors that are
|
||||
// also returned to a calling function.
|
||||
LogWarning
|
||||
|
||||
// LogInformational level is used for normal non-error activity
|
||||
LogInformational
|
||||
|
||||
// LogDebug level is for very detailed non-error activity. This is
|
||||
// very spammy and will impact performance.
|
||||
LogDebug
|
||||
)
|
||||
|
||||
// Logger can be used to replace the standard logging for discordgo
|
||||
var Logger func(msgL, caller int, format string, a ...interface{})
|
||||
|
||||
// msglog provides package wide logging consistency for discordgo
|
||||
// the format, a... portion this command follows that of fmt.Printf
|
||||
// msgL : LogLevel of the message
|
||||
// caller : 1 + the number of callers away from the message source
|
||||
// format : Printf style message format
|
||||
// a ... : comma separated list of values to pass
|
||||
func msglog(msgL, caller int, format string, a ...interface{}) {
|
||||
|
||||
if Logger != nil {
|
||||
Logger(msgL, caller, format, a...)
|
||||
} else {
|
||||
|
||||
pc, file, line, _ := runtime.Caller(caller)
|
||||
|
||||
files := strings.Split(file, "/")
|
||||
file = files[len(files)-1]
|
||||
|
||||
name := runtime.FuncForPC(pc).Name()
|
||||
fns := strings.Split(name, ".")
|
||||
name = fns[len(fns)-1]
|
||||
|
||||
msg := fmt.Sprintf(format, a...)
|
||||
|
||||
log.Printf("[DG%d] %s:%d:%s() %s\n", msgL, file, line, name, msg)
|
||||
}
|
||||
}
|
||||
|
||||
// helper function that wraps msglog for the Session struct
|
||||
// This adds a check to insure the message is only logged
|
||||
// if the session log level is equal or higher than the
|
||||
// message log level
|
||||
func (s *Session) log(msgL int, format string, a ...interface{}) {
|
||||
|
||||
if msgL > s.LogLevel {
|
||||
return
|
||||
}
|
||||
|
||||
msglog(msgL, 2, format, a...)
|
||||
}
|
||||
|
||||
// helper function that wraps msglog for the VoiceConnection struct
|
||||
// This adds a check to insure the message is only logged
|
||||
// if the voice connection log level is equal or higher than the
|
||||
// message log level
|
||||
func (v *VoiceConnection) log(msgL int, format string, a ...interface{}) {
|
||||
|
||||
if msgL > v.LogLevel {
|
||||
return
|
||||
}
|
||||
|
||||
msglog(msgL, 2, format, a...)
|
||||
}
|
||||
|
||||
// printJSON is a helper function to display JSON data in a easy to read format.
|
||||
/* NOT USED ATM
|
||||
func printJSON(body []byte) {
|
||||
var prettyJSON bytes.Buffer
|
||||
error := json.Indent(&prettyJSON, body, "", "\t")
|
||||
if error != nil {
|
||||
log.Print("JSON parse error: ", error)
|
||||
}
|
||||
log.Println(string(prettyJSON.Bytes()))
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,408 @@
|
|||
// Discordgo - Discord bindings for Go
|
||||
// Available at https://github.com/Bios-Marcel/discordgo
|
||||
|
||||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains code related to the Message struct
|
||||
|
||||
package discordgo
|
||||
|
||||
import (
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// MessageType is the type of Message
|
||||
type MessageType int
|
||||
|
||||
// Block contains the valid known MessageType values
|
||||
const (
|
||||
MessageTypeDefault MessageType = iota
|
||||
MessageTypeRecipientAdd
|
||||
MessageTypeRecipientRemove
|
||||
MessageTypeCall
|
||||
MessageTypeChannelNameChange
|
||||
MessageTypeChannelIconChange
|
||||
MessageTypeChannelPinnedMessage
|
||||
MessageTypeGuildMemberJoin
|
||||
MessageTypeUserPremiumGuildSubscription
|
||||
MessageTypeUserPremiumGuildSubscriptionTierOne
|
||||
MessageTypeUserPremiumGuildSubscriptionTierTwo
|
||||
MessageTypeUserPremiumGuildSubscriptionTierThree
|
||||
MessageTypeChannelFollowAdd
|
||||
)
|
||||
|
||||
// A Message stores all data related to a specific Discord message.
|
||||
type Message struct {
|
||||
// The ID of the message.
|
||||
ID string `json:"id"`
|
||||
|
||||
// The ID of the channel in which the message was sent.
|
||||
ChannelID string `json:"channel_id"`
|
||||
|
||||
// The ID of the guild in which the message was sent.
|
||||
GuildID string `json:"guild_id,omitempty"`
|
||||
|
||||
// The content of the message.
|
||||
Content string `json:"content"`
|
||||
|
||||
// The time at which the messsage was sent.
|
||||
// CAUTION: this field may be removed in a
|
||||
// future API version; it is safer to calculate
|
||||
// the creation time via the ID.
|
||||
Timestamp Timestamp `json:"timestamp"`
|
||||
|
||||
// The time at which the last edit of the message
|
||||
// occurred, if it has been edited.
|
||||
EditedTimestamp Timestamp `json:"edited_timestamp"`
|
||||
|
||||
// The roles mentioned in the message.
|
||||
MentionRoles []string `json:"mention_roles"`
|
||||
|
||||
// Whether the message is text-to-speech.
|
||||
TTS bool `json:"tts"`
|
||||
|
||||
// Whether the message mentions everyone.
|
||||
MentionEveryone bool `json:"mention_everyone"`
|
||||
|
||||
// The author of the message. This is not guaranteed to be a
|
||||
// valid user (webhook-sent messages do not possess a full author).
|
||||
Author *User `json:"author"`
|
||||
|
||||
// A list of attachments present in the message.
|
||||
Attachments []*MessageAttachment `json:"attachments"`
|
||||
|
||||
// A list of embeds present in the message. Multiple
|
||||
// embeds can currently only be sent by webhooks.
|
||||
Embeds []*MessageEmbed `json:"embeds"`
|
||||
|
||||
// A list of users mentioned in the message.
|
||||
Mentions []*User `json:"mentions"`
|
||||
|
||||
// A list of reactions to the message.
|
||||
Reactions []*MessageReactions `json:"reactions"`
|
||||
|
||||
// Whether the message is pinned or not.
|
||||
Pinned bool `json:"pinned"`
|
||||
|
||||
// The type of the message.
|
||||
Type MessageType `json:"type"`
|
||||
|
||||
// The webhook ID of the message, if it was generated by a webhook
|
||||
WebhookID string `json:"webhook_id"`
|
||||
|
||||
// Member properties for this message's author,
|
||||
// contains only partial information
|
||||
Member *Member `json:"member"`
|
||||
|
||||
// Channels specifically mentioned in this message
|
||||
// Not all channel mentions in a message will appear in mention_channels.
|
||||
// Only textual channels that are visible to everyone in a lurkable guild will ever be included.
|
||||
// Only crossposted messages (via Channel Following) currently include mention_channels at all.
|
||||
// If no mentions in the message meet these requirements, this field will not be sent.
|
||||
MentionChannels []*Channel `json:"mention_channels"`
|
||||
|
||||
// Is sent with Rich Presence-related chat embeds
|
||||
Activity *MessageActivity `json:"activity"`
|
||||
|
||||
// Is sent with Rich Presence-related chat embeds
|
||||
Application *MessageApplication `json:"application"`
|
||||
|
||||
// MessageReference contains reference data sent with crossposted messages
|
||||
MessageReference *MessageReference `json:"message_reference"`
|
||||
|
||||
// The flags of the message, which describe extra features of a message.
|
||||
// This is a combination of bit masks; the presence of a certain permission can
|
||||
// be checked by performing a bitwise AND between this int and the flag.
|
||||
Flags int `json:"flags"`
|
||||
}
|
||||
|
||||
// File stores info about files you e.g. send in messages.
|
||||
type File struct {
|
||||
Name string
|
||||
ContentType string
|
||||
Reader io.Reader
|
||||
}
|
||||
|
||||
// MessageSend stores all parameters you can send with ChannelMessageSendComplex.
|
||||
type MessageSend struct {
|
||||
Content string `json:"content,omitempty"`
|
||||
Embed *MessageEmbed `json:"embed,omitempty"`
|
||||
TTS bool `json:"tts"`
|
||||
Files []*File `json:"-"`
|
||||
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
|
||||
|
||||
// TODO: Remove this when compatibility is not required.
|
||||
File *File `json:"-"`
|
||||
}
|
||||
|
||||
// MessageEdit is used to chain parameters via ChannelMessageEditComplex, which
|
||||
// is also where you should get the instance from.
|
||||
type MessageEdit struct {
|
||||
Content *string `json:"content,omitempty"`
|
||||
Embed *MessageEmbed `json:"embed,omitempty"`
|
||||
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
|
||||
|
||||
ID string
|
||||
Channel string
|
||||
}
|
||||
|
||||
// NewMessageEdit returns a MessageEdit struct, initialized
|
||||
// with the Channel and ID.
|
||||
func NewMessageEdit(channelID string, messageID string) *MessageEdit {
|
||||
return &MessageEdit{
|
||||
Channel: channelID,
|
||||
ID: messageID,
|
||||
}
|
||||
}
|
||||
|
||||
// SetContent is the same as setting the variable Content,
|
||||
// except it doesn't take a pointer.
|
||||
func (m *MessageEdit) SetContent(str string) *MessageEdit {
|
||||
m.Content = &str
|
||||
return m
|
||||
}
|
||||
|
||||
// SetEmbed is a convenience function for setting the embed,
|
||||
// so you can chain commands.
|
||||
func (m *MessageEdit) SetEmbed(embed *MessageEmbed) *MessageEdit {
|
||||
m.Embed = embed
|
||||
return m
|
||||
}
|
||||
|
||||
// AllowedMentionType describes the types of mentions used
|
||||
// in the MessageAllowedMentions type.
|
||||
type AllowedMentionType string
|
||||
|
||||
// The types of mentions used in MessageAllowedMentions.
|
||||
const (
|
||||
AllowedMentionTypeRoles AllowedMentionType = "roles"
|
||||
AllowedMentionTypeUsers AllowedMentionType = "users"
|
||||
AllowedMentionTypeEveryone AllowedMentionType = "everyone"
|
||||
)
|
||||
|
||||
// MessageAllowedMentions allows the user to specify which mentions
|
||||
// Discord is allowed to parse in this message. This is useful when
|
||||
// sending user input as a message, as it prevents unwanted mentions.
|
||||
// If this type is used, all mentions must be explicitly whitelisted,
|
||||
// either by putting an AllowedMentionType in the Parse slice
|
||||
// (allowing all mentions of that type) or, in the case of roles and
|
||||
// users, explicitly allowing those mentions on an ID-by-ID basis.
|
||||
// For more information on this functionality, see:
|
||||
// https://discordapp.com/developers/docs/resources/channel#allowed-mentions-object-allowed-mentions-reference
|
||||
type MessageAllowedMentions struct {
|
||||
// The mention types that are allowed to be parsed in this message.
|
||||
// Please note that this is purposely **not** marked as omitempty,
|
||||
// so if a zero-value MessageAllowedMentions object is provided no
|
||||
// mentions will be allowed.
|
||||
Parse []AllowedMentionType `json:"parse"`
|
||||
|
||||
// A list of role IDs to allow. This cannot be used when specifying
|
||||
// AllowedMentionTypeRoles in the Parse slice.
|
||||
Roles []string `json:"roles,omitempty"`
|
||||
|
||||
// A list of user IDs to allow. This cannot be used when specifying
|
||||
// AllowedMentionTypeUsers in the Parse slice.
|
||||
Users []string `json:"users,omitempty"`
|
||||
}
|
||||
|
||||
// A MessageAttachment stores data for message attachments.
|
||||
type MessageAttachment struct {
|
||||
ID string `json:"id"`
|
||||
URL string `json:"url"`
|
||||
ProxyURL string `json:"proxy_url"`
|
||||
Filename string `json:"filename"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Size int `json:"size"`
|
||||
}
|
||||
|
||||
// MessageEmbedFooter is a part of a MessageEmbed struct.
|
||||
type MessageEmbedFooter struct {
|
||||
Text string `json:"text,omitempty"`
|
||||
IconURL string `json:"icon_url,omitempty"`
|
||||
ProxyIconURL string `json:"proxy_icon_url,omitempty"`
|
||||
}
|
||||
|
||||
// MessageEmbedImage is a part of a MessageEmbed struct.
|
||||
type MessageEmbedImage struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
ProxyURL string `json:"proxy_url,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
}
|
||||
|
||||
// MessageEmbedThumbnail is a part of a MessageEmbed struct.
|
||||
type MessageEmbedThumbnail struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
ProxyURL string `json:"proxy_url,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
}
|
||||
|
||||
// MessageEmbedVideo is a part of a MessageEmbed struct.
|
||||
type MessageEmbedVideo struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
ProxyURL string `json:"proxy_url,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
}
|
||||
|
||||
// MessageEmbedProvider is a part of a MessageEmbed struct.
|
||||
type MessageEmbedProvider struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// MessageEmbedAuthor is a part of a MessageEmbed struct.
|
||||
type MessageEmbedAuthor struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
IconURL string `json:"icon_url,omitempty"`
|
||||
ProxyIconURL string `json:"proxy_icon_url,omitempty"`
|
||||
}
|
||||
|
||||
// MessageEmbedField is a part of a MessageEmbed struct.
|
||||
type MessageEmbedField struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Value string `json:"value,omitempty"`
|
||||
Inline bool `json:"inline,omitempty"`
|
||||
}
|
||||
|
||||
// An MessageEmbed stores data for message embeds.
|
||||
type MessageEmbed struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Timestamp string `json:"timestamp,omitempty"`
|
||||
Color int `json:"color,omitempty"`
|
||||
Footer *MessageEmbedFooter `json:"footer,omitempty"`
|
||||
Image *MessageEmbedImage `json:"image,omitempty"`
|
||||
Thumbnail *MessageEmbedThumbnail `json:"thumbnail,omitempty"`
|
||||
Video *MessageEmbedVideo `json:"video,omitempty"`
|
||||
Provider *MessageEmbedProvider `json:"provider,omitempty"`
|
||||
Author *MessageEmbedAuthor `json:"author,omitempty"`
|
||||
Fields []*MessageEmbedField `json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
// MessageReactions holds a reactions object for a message.
|
||||
type MessageReactions struct {
|
||||
Count int `json:"count"`
|
||||
Me bool `json:"me"`
|
||||
Emoji *Emoji `json:"emoji"`
|
||||
}
|
||||
|
||||
// MessageActivity is sent with Rich Presence-related chat embeds
|
||||
type MessageActivity struct {
|
||||
Type MessageActivityType `json:"type"`
|
||||
PartyID string `json:"party_id"`
|
||||
}
|
||||
|
||||
// MessageActivityType is the type of message activity
|
||||
type MessageActivityType int
|
||||
|
||||
// Constants for the different types of Message Activity
|
||||
const (
|
||||
MessageActivityTypeJoin = iota + 1
|
||||
MessageActivityTypeSpectate
|
||||
MessageActivityTypeListen
|
||||
MessageActivityTypeJoinRequest
|
||||
)
|
||||
|
||||
// MessageFlag describes an extra feature of the message
|
||||
type MessageFlag int
|
||||
|
||||
// Constants for the different bit offsets of Message Flags
|
||||
const (
|
||||
// This message has been published to subscribed channels (via Channel Following)
|
||||
MessageFlagCrossposted = 1 << iota
|
||||
// This message originated from a message in another channel (via Channel Following)
|
||||
MessageFlagIsCrosspost
|
||||
// Do not include any embeds when serializing this message
|
||||
MessageFlagSuppressEmbeds
|
||||
)
|
||||
|
||||
// MessageApplication is sent with Rich Presence-related chat embeds
|
||||
type MessageApplication struct {
|
||||
ID string `json:"id"`
|
||||
CoverImage string `json:"cover_image"`
|
||||
Description string `json:"description"`
|
||||
Icon string `json:"icon"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// MessageReference contains reference data sent with crossposted messages
|
||||
type MessageReference struct {
|
||||
MessageID string `json:"message_id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
GuildID string `json:"guild_id"`
|
||||
}
|
||||
|
||||
// ContentWithMentionsReplaced will replace all @<id> mentions with the
|
||||
// username of the mention.
|
||||
func (m *Message) ContentWithMentionsReplaced() (content string) {
|
||||
content = m.Content
|
||||
|
||||
for _, user := range m.Mentions {
|
||||
content = strings.NewReplacer(
|
||||
"<@"+user.ID+">", "@"+user.Username,
|
||||
"<@!"+user.ID+">", "@"+user.Username,
|
||||
).Replace(content)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var patternChannels = regexp.MustCompile("<#[^>]*>")
|
||||
|
||||
// ContentWithMoreMentionsReplaced will replace all @<id> mentions with the
|
||||
// username of the mention, but also role IDs and more.
|
||||
func (m *Message) ContentWithMoreMentionsReplaced(s *Session) (content string, err error) {
|
||||
content = m.Content
|
||||
|
||||
if !s.StateEnabled {
|
||||
content = m.ContentWithMentionsReplaced()
|
||||
return
|
||||
}
|
||||
|
||||
channel, err := s.State.Channel(m.ChannelID)
|
||||
if err != nil {
|
||||
content = m.ContentWithMentionsReplaced()
|
||||
return
|
||||
}
|
||||
|
||||
for _, user := range m.Mentions {
|
||||
nick := user.Username
|
||||
|
||||
member, err := s.State.Member(channel.GuildID, user.ID)
|
||||
if err == nil && member.Nick != "" {
|
||||
nick = member.Nick
|
||||
}
|
||||
|
||||
content = strings.NewReplacer(
|
||||
"<@"+user.ID+">", "@"+user.Username,
|
||||
"<@!"+user.ID+">", "@"+nick,
|
||||
).Replace(content)
|
||||
}
|
||||
for _, roleID := range m.MentionRoles {
|
||||
role, err := s.State.Role(channel.GuildID, roleID)
|
||||
if err != nil || !role.Mentionable {
|
||||
continue
|
||||
}
|
||||
|
||||
content = strings.Replace(content, "<@&"+role.ID+">", "@"+role.Name, -1)
|
||||
}
|
||||
|
||||
content = patternChannels.ReplaceAllStringFunc(content, func(mention string) string {
|
||||
channel, err := s.State.Channel(mention[2 : len(mention)-1])
|
||||
if err != nil || channel.Type == ChannelTypeGuildVoice {
|
||||
return mention
|
||||
}
|
||||
|
||||
return "#" + channel.Name
|
||||
})
|
||||
return
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
// Discordgo - Discord bindings for Go
|
||||
// Available at https://github.com/Bios-Marcel/discordgo
|
||||
|
||||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains functions related to Discord OAuth2 endpoints
|
||||
|
||||
package discordgo
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Code specific to Discord OAuth2 Applications
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
// An Application struct stores values for a Discord OAuth2 Application
|
||||
type Application struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
Secret string `json:"secret,omitempty"`
|
||||
RedirectURIs *[]string `json:"redirect_uris,omitempty"`
|
||||
BotRequireCodeGrant bool `json:"bot_require_code_grant,omitempty"`
|
||||
BotPublic bool `json:"bot_public,omitempty"`
|
||||
RPCApplicationState int `json:"rpc_application_state,omitempty"`
|
||||
Flags int `json:"flags,omitempty"`
|
||||
Owner *User `json:"owner"`
|
||||
Bot *User `json:"bot"`
|
||||
}
|
||||
|
||||
// Application returns an Application structure of a specific Application
|
||||
// appID : The ID of an Application
|
||||
func (s *Session) Application(appID string) (st *Application, err error) {
|
||||
|
||||
body, err := s.RequestWithBucketID("GET", EndpointApplication(appID), nil, EndpointApplication(""))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = unmarshal(body, &st)
|
||||
return
|
||||
}
|
||||
|
||||
// Applications returns all applications for the authenticated user
|
||||
func (s *Session) Applications() (st []*Application, err error) {
|
||||
|
||||
body, err := s.RequestWithBucketID("GET", EndpointApplications, nil, EndpointApplications)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = unmarshal(body, &st)
|
||||
return
|
||||
}
|
||||
|
||||
// ApplicationCreate creates a new Application
|
||||
// name : Name of Application / Bot
|
||||
// uris : Redirect URIs (Not required)
|
||||
func (s *Session) ApplicationCreate(ap *Application) (st *Application, err error) {
|
||||
|
||||
data := struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
RedirectURIs *[]string `json:"redirect_uris,omitempty"`
|
||||
}{ap.Name, ap.Description, ap.RedirectURIs}
|
||||
|
||||
body, err := s.RequestWithBucketID("POST", EndpointApplications, data, EndpointApplications)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = unmarshal(body, &st)
|
||||
return
|
||||
}
|
||||
|
||||
// ApplicationUpdate updates an existing Application
|
||||
// var : desc
|
||||
func (s *Session) ApplicationUpdate(appID string, ap *Application) (st *Application, err error) {
|
||||
|
||||
data := struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
RedirectURIs *[]string `json:"redirect_uris,omitempty"`
|
||||
}{ap.Name, ap.Description, ap.RedirectURIs}
|
||||
|
||||
body, err := s.RequestWithBucketID("PUT", EndpointApplication(appID), data, EndpointApplication(""))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = unmarshal(body, &st)
|
||||
return
|
||||
}
|
||||
|
||||
// ApplicationDelete deletes an existing Application
|
||||
// appID : The ID of an Application
|
||||
func (s *Session) ApplicationDelete(appID string) (err error) {
|
||||
|
||||
_, err = s.RequestWithBucketID("DELETE", EndpointApplication(appID), nil, EndpointApplication(""))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Asset struct stores values for an asset of an application
|
||||
type Asset struct {
|
||||
Type int `json:"type"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// ApplicationAssets returns an application's assets
|
||||
func (s *Session) ApplicationAssets(appID string) (ass []*Asset, err error) {
|
||||
|
||||
body, err := s.RequestWithBucketID("GET", EndpointApplicationAssets(appID), nil, EndpointApplicationAssets(""))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = unmarshal(body, &ass)
|
||||
return
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Code specific to Discord OAuth2 Application Bots
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
// ApplicationBotCreate creates an Application Bot Account
|
||||
//
|
||||
// appID : The ID of an Application
|
||||
//
|
||||
// NOTE: func name may change, if I can think up something better.
|
||||
func (s *Session) ApplicationBotCreate(appID string) (st *User, err error) {
|
||||
|
||||
body, err := s.RequestWithBucketID("POST", EndpointApplicationsBot(appID), nil, EndpointApplicationsBot(""))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = unmarshal(body, &st)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
package discordgo
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// customRateLimit holds information for defining a custom rate limit
|
||||
type customRateLimit struct {
|
||||
suffix string
|
||||
requests int
|
||||
reset time.Duration
|
||||
}
|
||||
|
||||
// RateLimiter holds all ratelimit buckets
|
||||
type RateLimiter struct {
|
||||
sync.Mutex
|
||||
global *int64
|
||||
buckets map[string]*Bucket
|
||||
globalRateLimit time.Duration
|
||||
customRateLimits []*customRateLimit
|
||||
}
|
||||
|
||||
// NewRatelimiter returns a new RateLimiter
|
||||
func NewRatelimiter() *RateLimiter {
|
||||
|
||||
return &RateLimiter{
|
||||
buckets: make(map[string]*Bucket),
|
||||
global: new(int64),
|
||||
customRateLimits: []*customRateLimit{
|
||||
&customRateLimit{
|
||||
suffix: "//reactions//",
|
||||
requests: 1,
|
||||
reset: 200 * time.Millisecond,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetBucket retrieves or creates a bucket
|
||||
func (r *RateLimiter) GetBucket(key string) *Bucket {
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
|
||||
if bucket, ok := r.buckets[key]; ok {
|
||||
return bucket
|
||||
}
|
||||
|
||||
b := &Bucket{
|
||||
Remaining: 1,
|
||||
Key: key,
|
||||
global: r.global,
|
||||
}
|
||||
|
||||
// Check if there is a custom ratelimit set for this bucket ID.
|
||||
for _, rl := range r.customRateLimits {
|
||||
if strings.HasSuffix(b.Key, rl.suffix) {
|
||||
b.customRateLimit = rl
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
r.buckets[key] = b
|
||||
return b
|
||||
}
|
||||
|
||||
// GetWaitTime returns the duration you should wait for a Bucket
|
||||
func (r *RateLimiter) GetWaitTime(b *Bucket, minRemaining int) time.Duration {
|
||||
// If we ran out of calls and the reset time is still ahead of us
|
||||
// then we need to take it easy and relax a little
|
||||
if b.Remaining < minRemaining && b.reset.After(time.Now()) {
|
||||
return b.reset.Sub(time.Now())
|
||||
}
|
||||
|
||||
// Check for global ratelimits
|
||||
sleepTo := time.Unix(0, atomic.LoadInt64(r.global))
|
||||
if now := time.Now(); now.Before(sleepTo) {
|
||||
return sleepTo.Sub(now)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// LockBucket Locks until a request can be made
|
||||
func (r *RateLimiter) LockBucket(bucketID string) *Bucket {
|
||||
return r.LockBucketObject(r.GetBucket(bucketID))
|
||||
}
|
||||
|
||||
// LockBucketObject Locks an already resolved bucket until a request can be made
|
||||
func (r *RateLimiter) LockBucketObject(b *Bucket) *Bucket {
|
||||
b.Lock()
|
||||
|
||||
if wait := r.GetWaitTime(b, 1); wait > 0 {
|
||||
time.Sleep(wait)
|
||||
}
|
||||
|
||||
b.Remaining--
|
||||
return b
|
||||
}
|
||||
|
||||
// Bucket represents a ratelimit bucket, each bucket gets ratelimited individually (-global ratelimits)
|
||||
type Bucket struct {
|
||||
sync.Mutex
|
||||
Key string
|
||||
Remaining int
|
||||
limit int
|
||||
reset time.Time
|
||||
global *int64
|
||||
|
||||
lastReset time.Time
|
||||
customRateLimit *customRateLimit
|
||||
Userdata interface{}
|
||||
}
|
||||
|
||||
// Release unlocks the bucket and reads the headers to update the buckets ratelimit info
|
||||
// and locks up the whole thing in case if there's a global ratelimit.
|
||||
func (b *Bucket) Release(headers http.Header) error {
|
||||
defer b.Unlock()
|
||||
|
||||
// Check if the bucket uses a custom ratelimiter
|
||||
if rl := b.customRateLimit; rl != nil {
|
||||
if time.Now().Sub(b.lastReset) >= rl.reset {
|
||||
b.Remaining = rl.requests - 1
|
||||
b.lastReset = time.Now()
|
||||
}
|
||||
if b.Remaining < 1 {
|
||||
b.reset = time.Now().Add(rl.reset)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if headers == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
remaining := headers.Get("X-RateLimit-Remaining")
|
||||
reset := headers.Get("X-RateLimit-Reset")
|
||||
global := headers.Get("X-RateLimit-Global")
|
||||
retryAfter := headers.Get("Retry-After")
|
||||
|
||||
// Update global and per bucket reset time if the proper headers are available
|
||||
// If global is set, then it will block all buckets until after Retry-After
|
||||
// If Retry-After without global is provided it will use that for the new reset
|
||||
// time since it's more accurate than X-RateLimit-Reset.
|
||||
// If Retry-After after is not proided, it will update the reset time from X-RateLimit-Reset
|
||||
if retryAfter != "" {
|
||||
parsedAfter, err := strconv.ParseInt(retryAfter, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resetAt := time.Now().Add(time.Duration(parsedAfter) * time.Millisecond)
|
||||
|
||||
// Lock either this single bucket or all buckets
|
||||
if global != "" {
|
||||
atomic.StoreInt64(b.global, resetAt.UnixNano())
|
||||
} else {
|
||||
b.reset = resetAt
|
||||
}
|
||||
} else if reset != "" {
|
||||
// Calculate the reset time by using the date header returned from discord
|
||||
discordTime, err := http.ParseTime(headers.Get("Date"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
unix, err := strconv.ParseInt(reset, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Calculate the time until reset and add it to the current local time
|
||||
// some extra time is added because without it i still encountered 429's.
|
||||
// The added amount is the lowest amount that gave no 429's
|
||||
// in 1k requests
|
||||
delta := time.Unix(unix, 0).Sub(discordTime) + time.Millisecond*250
|
||||
b.reset = time.Now().Add(delta)
|
||||
}
|
||||
|
||||
// Udpate remaining if header is present
|
||||
if remaining != "" {
|
||||
parsedRemaining, err := strconv.ParseInt(remaining, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.Remaining = int(parsedRemaining)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,57 @@
|
|||
// Discordgo - Discord bindings for Go
|
||||
// Available at https://github.com/Bios-Marcel/discordgo
|
||||
|
||||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains custom types, currently only a timestamp wrapper.
|
||||
|
||||
package discordgo
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Timestamp stores a timestamp, as sent by the Discord API.
|
||||
type Timestamp string
|
||||
|
||||
// Parse parses a timestamp string into a time.Time object.
|
||||
// The only time this can fail is if Discord changes their timestamp format.
|
||||
func (t Timestamp) Parse() (time.Time, error) {
|
||||
return time.Parse(time.RFC3339, string(t))
|
||||
}
|
||||
|
||||
// RESTError stores error information about a request with a bad response code.
|
||||
// Message is not always present, there are cases where api calls can fail
|
||||
// without returning a json message.
|
||||
type RESTError struct {
|
||||
Request *http.Request
|
||||
Response *http.Response
|
||||
ResponseBody []byte
|
||||
|
||||
Message *APIErrorMessage // Message may be nil.
|
||||
}
|
||||
|
||||
func newRestError(req *http.Request, resp *http.Response, body []byte) *RESTError {
|
||||
restErr := &RESTError{
|
||||
Request: req,
|
||||
Response: resp,
|
||||
ResponseBody: body,
|
||||
}
|
||||
|
||||
// Attempt to decode the error and assume no message was provided if it fails
|
||||
var msg *APIErrorMessage
|
||||
err := json.Unmarshal(body, &msg)
|
||||
if err == nil {
|
||||
restErr.Message = msg
|
||||
}
|
||||
|
||||
return restErr
|
||||
}
|
||||
|
||||
func (r RESTError) Error() string {
|
||||
return "HTTP " + r.Response.Status + ", " + string(r.ResponseBody)
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package discordgo
|
||||
|
||||
import "strings"
|
||||
|
||||
type UserPremiumType int
|
||||
|
||||
const (
|
||||
UserPremiumTypeNone UserPremiumType = iota
|
||||
UserPremiumTypeNitroClassic
|
||||
UserPremiumTypeNitro
|
||||
)
|
||||
|
||||
// A User stores all data for an individual Discord user.
|
||||
type User struct {
|
||||
// The ID of the user.
|
||||
ID string `json:"id"`
|
||||
|
||||
// The email of the user. This is only present when
|
||||
// the application possesses the email scope for the user.
|
||||
Email string `json:"email"`
|
||||
|
||||
// The user's username.
|
||||
Username string `json:"username"`
|
||||
|
||||
// The hash of the user's avatar. Use Session.UserAvatar
|
||||
// to retrieve the avatar itself.
|
||||
Avatar string `json:"avatar"`
|
||||
|
||||
// The user's chosen language option.
|
||||
Locale string `json:"locale"`
|
||||
|
||||
// The discriminator of the user (4 numbers after name).
|
||||
Discriminator string `json:"discriminator"`
|
||||
|
||||
// The token of the user. This is only present for
|
||||
// the user represented by the current session.
|
||||
Token string `json:"token"`
|
||||
|
||||
// Whether the user's email is verified.
|
||||
Verified bool `json:"verified"`
|
||||
|
||||
// Whether the user has multi-factor authentication enabled.
|
||||
MFAEnabled bool `json:"mfa_enabled"`
|
||||
|
||||
// Whether the user is a bot.
|
||||
Bot bool `json:"bot"`
|
||||
|
||||
// PremiumType indicates whether the user has Discord Nitro and if
|
||||
// so, which type of Discord Nitro.
|
||||
PremiumType UserPremiumType `json:"premium_type"`
|
||||
}
|
||||
|
||||
// String returns a unique identifier of the form username#discriminator
|
||||
func (u *User) String() string {
|
||||
return u.Username + "#" + u.Discriminator
|
||||
}
|
||||
|
||||
// Mention return a string which mentions the user
|
||||
func (u *User) Mention() string {
|
||||
return "<@" + u.ID + ">"
|
||||
}
|
||||
|
||||
// AvatarURL returns a URL to the user's avatar.
|
||||
// size: The size of the user's avatar as a power of two
|
||||
// if size is an empty string, no size parameter will
|
||||
// be added to the URL.
|
||||
func (u *User) AvatarURL(size string) string {
|
||||
var URL string
|
||||
if u.Avatar == "" {
|
||||
URL = EndpointDefaultUserAvatar(u.Discriminator)
|
||||
} else if strings.HasPrefix(u.Avatar, "a_") {
|
||||
URL = EndpointUserAvatarAnimated(u.ID, u.Avatar)
|
||||
} else {
|
||||
URL = EndpointUserAvatar(u.ID, u.Avatar)
|
||||
}
|
||||
|
||||
if size != "" {
|
||||
return URL + "?size=" + size
|
||||
}
|
||||
return URL
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package discordgo
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SnowflakeTimestamp returns the creation time of a Snowflake ID relative to the creation of Discord.
|
||||
func SnowflakeTimestamp(ID string) (t time.Time, err error) {
|
||||
i, err := strconv.ParseInt(ID, 10, 64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
timestamp := (i >> 22) + 1420070400000
|
||||
t = time.Unix(0, timestamp*1000000)
|
||||
return
|
||||
}
|
|
@ -0,0 +1,906 @@
|
|||
// Discordgo - Discord bindings for Go
|
||||
// Available at https://github.com/Bios-Marcel/discordgo
|
||||
|
||||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains code related to Discord voice suppport
|
||||
|
||||
package discordgo
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Code related to both VoiceConnection Websocket and UDP connections.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
// A VoiceConnection struct holds all the data and functions related to a Discord Voice Connection.
|
||||
type VoiceConnection struct {
|
||||
sync.RWMutex
|
||||
|
||||
Debug bool // If true, print extra logging -- DEPRECATED
|
||||
LogLevel int
|
||||
Ready bool // If true, voice is ready to send/receive audio
|
||||
UserID string
|
||||
GuildID string
|
||||
ChannelID string
|
||||
deaf bool
|
||||
mute bool
|
||||
speaking bool
|
||||
reconnecting bool // If true, voice connection is trying to reconnect
|
||||
|
||||
OpusSend chan []byte // Chan for sending opus audio
|
||||
OpusRecv chan *Packet // Chan for receiving opus audio
|
||||
|
||||
wsConn *websocket.Conn
|
||||
wsMutex sync.Mutex
|
||||
udpConn *net.UDPConn
|
||||
session *Session
|
||||
|
||||
sessionID string
|
||||
token string
|
||||
endpoint string
|
||||
|
||||
// Used to send a close signal to goroutines
|
||||
close chan struct{}
|
||||
|
||||
// Used to allow blocking until connected
|
||||
connected chan bool
|
||||
|
||||
// Used to pass the sessionid from onVoiceStateUpdate
|
||||
// sessionRecv chan string UNUSED ATM
|
||||
|
||||
op4 voiceOP4
|
||||
op2 voiceOP2
|
||||
|
||||
voiceSpeakingUpdateHandlers []VoiceSpeakingUpdateHandler
|
||||
}
|
||||
|
||||
// VoiceSpeakingUpdateHandler type provides a function definition for the
|
||||
// VoiceSpeakingUpdate event
|
||||
type VoiceSpeakingUpdateHandler func(vc *VoiceConnection, vs *VoiceSpeakingUpdate)
|
||||
|
||||
// Speaking sends a speaking notification to Discord over the voice websocket.
|
||||
// This must be sent as true prior to sending audio and should be set to false
|
||||
// once finished sending audio.
|
||||
// b : Send true if speaking, false if not.
|
||||
func (v *VoiceConnection) Speaking(b bool) (err error) {
|
||||
|
||||
v.log(LogDebug, "called (%t)", b)
|
||||
|
||||
type voiceSpeakingData struct {
|
||||
Speaking bool `json:"speaking"`
|
||||
Delay int `json:"delay"`
|
||||
}
|
||||
|
||||
type voiceSpeakingOp struct {
|
||||
Op int `json:"op"` // Always 5
|
||||
Data voiceSpeakingData `json:"d"`
|
||||
}
|
||||
|
||||
if v.wsConn == nil {
|
||||
return fmt.Errorf("no VoiceConnection websocket")
|
||||
}
|
||||
|
||||
data := voiceSpeakingOp{5, voiceSpeakingData{b, 0}}
|
||||
v.wsMutex.Lock()
|
||||
err = v.wsConn.WriteJSON(data)
|
||||
v.wsMutex.Unlock()
|
||||
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
if err != nil {
|
||||
v.speaking = false
|
||||
v.log(LogError, "Speaking() write json error, %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
v.speaking = b
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ChangeChannel sends Discord a request to change channels within a Guild
|
||||
// !!! NOTE !!! This function may be removed in favour of just using ChannelVoiceJoin
|
||||
func (v *VoiceConnection) ChangeChannel(channelID string, mute, deaf bool) (err error) {
|
||||
|
||||
v.log(LogInformational, "called")
|
||||
|
||||
data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, &channelID, mute, deaf}}
|
||||
v.wsMutex.Lock()
|
||||
err = v.session.wsConn.WriteJSON(data)
|
||||
v.wsMutex.Unlock()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
v.ChannelID = channelID
|
||||
v.deaf = deaf
|
||||
v.mute = mute
|
||||
v.speaking = false
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Disconnect disconnects from this voice channel and closes the websocket
|
||||
// and udp connections to Discord.
|
||||
func (v *VoiceConnection) Disconnect() (err error) {
|
||||
|
||||
// Send a OP4 with a nil channel to disconnect
|
||||
if v.sessionID != "" {
|
||||
data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}}
|
||||
v.session.wsMutex.Lock()
|
||||
err = v.session.wsConn.WriteJSON(data)
|
||||
v.session.wsMutex.Unlock()
|
||||
v.sessionID = ""
|
||||
}
|
||||
|
||||
// Close websocket and udp connections
|
||||
v.Close()
|
||||
|
||||
v.log(LogInformational, "Deleting VoiceConnection %s", v.GuildID)
|
||||
|
||||
v.session.Lock()
|
||||
delete(v.session.VoiceConnections, v.GuildID)
|
||||
v.session.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Close closes the voice ws and udp connections
|
||||
func (v *VoiceConnection) Close() {
|
||||
|
||||
v.log(LogInformational, "called")
|
||||
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
|
||||
v.Ready = false
|
||||
v.speaking = false
|
||||
|
||||
if v.close != nil {
|
||||
v.log(LogInformational, "closing v.close")
|
||||
close(v.close)
|
||||
v.close = nil
|
||||
}
|
||||
|
||||
if v.udpConn != nil {
|
||||
v.log(LogInformational, "closing udp")
|
||||
err := v.udpConn.Close()
|
||||
if err != nil {
|
||||
v.log(LogError, "error closing udp connection, %s", err)
|
||||
}
|
||||
v.udpConn = nil
|
||||
}
|
||||
|
||||
if v.wsConn != nil {
|
||||
v.log(LogInformational, "sending close frame")
|
||||
|
||||
// To cleanly close a connection, a client should send a close
|
||||
// frame and wait for the server to close the connection.
|
||||
v.wsMutex.Lock()
|
||||
err := v.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
|
||||
v.wsMutex.Unlock()
|
||||
if err != nil {
|
||||
v.log(LogError, "error closing websocket, %s", err)
|
||||
}
|
||||
|
||||
// TODO: Wait for Discord to actually close the connection.
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
v.log(LogInformational, "closing websocket")
|
||||
err = v.wsConn.Close()
|
||||
if err != nil {
|
||||
v.log(LogError, "error closing websocket, %s", err)
|
||||
}
|
||||
|
||||
v.wsConn = nil
|
||||
}
|
||||
}
|
||||
|
||||
// AddHandler adds a Handler for VoiceSpeakingUpdate events.
|
||||
func (v *VoiceConnection) AddHandler(h VoiceSpeakingUpdateHandler) {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
|
||||
v.voiceSpeakingUpdateHandlers = append(v.voiceSpeakingUpdateHandlers, h)
|
||||
}
|
||||
|
||||
// VoiceSpeakingUpdate is a struct for a VoiceSpeakingUpdate event.
|
||||
type VoiceSpeakingUpdate struct {
|
||||
UserID string `json:"user_id"`
|
||||
SSRC int `json:"ssrc"`
|
||||
Speaking bool `json:"speaking"`
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Unexported Internal Functions Below.
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
// A voiceOP4 stores the data for the voice operation 4 websocket event
|
||||
// which provides us with the NaCl SecretBox encryption key
|
||||
type voiceOP4 struct {
|
||||
SecretKey [32]byte `json:"secret_key"`
|
||||
Mode string `json:"mode"`
|
||||
}
|
||||
|
||||
// A voiceOP2 stores the data for the voice operation 2 websocket event
|
||||
// which is sort of like the voice READY packet
|
||||
type voiceOP2 struct {
|
||||
SSRC uint32 `json:"ssrc"`
|
||||
Port int `json:"port"`
|
||||
Modes []string `json:"modes"`
|
||||
HeartbeatInterval time.Duration `json:"heartbeat_interval"`
|
||||
IP string `json:"ip"`
|
||||
}
|
||||
|
||||
// WaitUntilConnected waits for the Voice Connection to
|
||||
// become ready, if it does not become ready it returns an err
|
||||
func (v *VoiceConnection) waitUntilConnected() error {
|
||||
|
||||
v.log(LogInformational, "called")
|
||||
|
||||
i := 0
|
||||
for {
|
||||
v.RLock()
|
||||
ready := v.Ready
|
||||
v.RUnlock()
|
||||
if ready {
|
||||
return nil
|
||||
}
|
||||
|
||||
if i > 10 {
|
||||
return fmt.Errorf("timeout waiting for voice")
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
// Open opens a voice connection. This should be called
|
||||
// after VoiceChannelJoin is used and the data VOICE websocket events
|
||||
// are captured.
|
||||
func (v *VoiceConnection) open() (err error) {
|
||||
|
||||
v.log(LogInformational, "called")
|
||||
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
|
||||
// Don't open a websocket if one is already open
|
||||
if v.wsConn != nil {
|
||||
v.log(LogWarning, "refusing to overwrite non-nil websocket")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO temp? loop to wait for the SessionID
|
||||
i := 0
|
||||
for {
|
||||
if v.sessionID != "" {
|
||||
break
|
||||
}
|
||||
if i > 20 { // only loop for up to 1 second total
|
||||
return fmt.Errorf("did not receive voice Session ID in time")
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
i++
|
||||
}
|
||||
|
||||
// Connect to VoiceConnection Websocket
|
||||
vg := "wss://" + strings.TrimSuffix(v.endpoint, ":80")
|
||||
v.log(LogInformational, "connecting to voice endpoint %s", vg)
|
||||
v.wsConn, _, err = websocket.DefaultDialer.Dial(vg, nil)
|
||||
if err != nil {
|
||||
v.log(LogWarning, "error connecting to voice endpoint %s, %s", vg, err)
|
||||
v.log(LogDebug, "voice struct: %#v\n", v)
|
||||
return
|
||||
}
|
||||
|
||||
type voiceHandshakeData struct {
|
||||
ServerID string `json:"server_id"`
|
||||
UserID string `json:"user_id"`
|
||||
SessionID string `json:"session_id"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
type voiceHandshakeOp struct {
|
||||
Op int `json:"op"` // Always 0
|
||||
Data voiceHandshakeData `json:"d"`
|
||||
}
|
||||
data := voiceHandshakeOp{0, voiceHandshakeData{v.GuildID, v.UserID, v.sessionID, v.token}}
|
||||
|
||||
err = v.wsConn.WriteJSON(data)
|
||||
if err != nil {
|
||||
v.log(LogWarning, "error sending init packet, %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
v.close = make(chan struct{})
|
||||
go v.wsListen(v.wsConn, v.close)
|
||||
|
||||
// add loop/check for Ready bool here?
|
||||
// then return false if not ready?
|
||||
// but then wsListen will also err.
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// wsListen listens on the voice websocket for messages and passes them
|
||||
// to the voice event handler. This is automatically called by the Open func
|
||||
func (v *VoiceConnection) wsListen(wsConn *websocket.Conn, close <-chan struct{}) {
|
||||
|
||||
v.log(LogInformational, "called")
|
||||
|
||||
for {
|
||||
_, message, err := v.wsConn.ReadMessage()
|
||||
if err != nil {
|
||||
// 4014 indicates a manual disconnection by someone in the guild;
|
||||
// we shouldn't reconnect.
|
||||
if websocket.IsCloseError(err, 4014) {
|
||||
v.log(LogInformational, "received 4014 manual disconnection")
|
||||
|
||||
// Abandon the voice WS connection
|
||||
v.Lock()
|
||||
v.wsConn = nil
|
||||
v.Unlock()
|
||||
|
||||
v.session.Lock()
|
||||
delete(v.session.VoiceConnections, v.GuildID)
|
||||
v.session.Unlock()
|
||||
|
||||
v.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Detect if we have been closed manually. If a Close() has already
|
||||
// happened, the websocket we are listening on will be different to the
|
||||
// current session.
|
||||
v.RLock()
|
||||
sameConnection := v.wsConn == wsConn
|
||||
v.RUnlock()
|
||||
if sameConnection {
|
||||
|
||||
v.log(LogError, "voice endpoint %s websocket closed unexpectantly, %s", v.endpoint, err)
|
||||
|
||||
// Start reconnect goroutine then exit.
|
||||
go v.reconnect()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Pass received message to voice event handler
|
||||
select {
|
||||
case <-close:
|
||||
return
|
||||
default:
|
||||
go v.onEvent(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wsEvent handles any voice websocket events. This is only called by the
|
||||
// wsListen() function.
|
||||
func (v *VoiceConnection) onEvent(message []byte) {
|
||||
|
||||
v.log(LogDebug, "received: %s", string(message))
|
||||
|
||||
var e Event
|
||||
if err := json.Unmarshal(message, &e); err != nil {
|
||||
v.log(LogError, "unmarshall error, %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
switch e.Operation {
|
||||
|
||||
case 2: // READY
|
||||
|
||||
if err := json.Unmarshal(e.RawData, &v.op2); err != nil {
|
||||
v.log(LogError, "OP2 unmarshall error, %s, %s", err, string(e.RawData))
|
||||
return
|
||||
}
|
||||
|
||||
// Start the voice websocket heartbeat to keep the connection alive
|
||||
go v.wsHeartbeat(v.wsConn, v.close, v.op2.HeartbeatInterval)
|
||||
// TODO monitor a chan/bool to verify this was successful
|
||||
|
||||
// Start the UDP connection
|
||||
err := v.udpOpen()
|
||||
if err != nil {
|
||||
v.log(LogError, "error opening udp connection, %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Start the opusSender.
|
||||
// TODO: Should we allow 48000/960 values to be user defined?
|
||||
if v.OpusSend == nil {
|
||||
v.OpusSend = make(chan []byte, 2)
|
||||
}
|
||||
go v.opusSender(v.udpConn, v.close, v.OpusSend, 48000, 960)
|
||||
|
||||
// Start the opusReceiver
|
||||
if !v.deaf {
|
||||
if v.OpusRecv == nil {
|
||||
v.OpusRecv = make(chan *Packet, 2)
|
||||
}
|
||||
|
||||
go v.opusReceiver(v.udpConn, v.close, v.OpusRecv)
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
case 3: // HEARTBEAT response
|
||||
// add code to use this to track latency?
|
||||
return
|
||||
|
||||
case 4: // udp encryption secret key
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
|
||||
v.op4 = voiceOP4{}
|
||||
if err := json.Unmarshal(e.RawData, &v.op4); err != nil {
|
||||
v.log(LogError, "OP4 unmarshall error, %s, %s", err, string(e.RawData))
|
||||
return
|
||||
}
|
||||
return
|
||||
|
||||
case 5:
|
||||
if len(v.voiceSpeakingUpdateHandlers) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
voiceSpeakingUpdate := &VoiceSpeakingUpdate{}
|
||||
if err := json.Unmarshal(e.RawData, voiceSpeakingUpdate); err != nil {
|
||||
v.log(LogError, "OP5 unmarshall error, %s, %s", err, string(e.RawData))
|
||||
return
|
||||
}
|
||||
|
||||
for _, h := range v.voiceSpeakingUpdateHandlers {
|
||||
h(v, voiceSpeakingUpdate)
|
||||
}
|
||||
|
||||
default:
|
||||
v.log(LogDebug, "unknown voice operation, %d, %s", e.Operation, string(e.RawData))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type voiceHeartbeatOp struct {
|
||||
Op int `json:"op"` // Always 3
|
||||
Data int `json:"d"`
|
||||
}
|
||||
|
||||
// NOTE :: When a guild voice server changes how do we shut this down
|
||||
// properly, so a new connection can be setup without fuss?
|
||||
//
|
||||
// wsHeartbeat sends regular heartbeats to voice Discord so it knows the client
|
||||
// is still connected. If you do not send these heartbeats Discord will
|
||||
// disconnect the websocket connection after a few seconds.
|
||||
func (v *VoiceConnection) wsHeartbeat(wsConn *websocket.Conn, close <-chan struct{}, i time.Duration) {
|
||||
|
||||
if close == nil || wsConn == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
ticker := time.NewTicker(i * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
v.log(LogDebug, "sending heartbeat packet")
|
||||
v.wsMutex.Lock()
|
||||
err = wsConn.WriteJSON(voiceHeartbeatOp{3, int(time.Now().Unix())})
|
||||
v.wsMutex.Unlock()
|
||||
if err != nil {
|
||||
v.log(LogError, "error sending heartbeat to voice endpoint %s, %s", v.endpoint, err)
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// continue loop and send heartbeat
|
||||
case <-close:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Code related to the VoiceConnection UDP connection
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
type voiceUDPData struct {
|
||||
Address string `json:"address"` // Public IP of machine running this code
|
||||
Port uint16 `json:"port"` // UDP Port of machine running this code
|
||||
Mode string `json:"mode"` // always "xsalsa20_poly1305"
|
||||
}
|
||||
|
||||
type voiceUDPD struct {
|
||||
Protocol string `json:"protocol"` // Always "udp" ?
|
||||
Data voiceUDPData `json:"data"`
|
||||
}
|
||||
|
||||
type voiceUDPOp struct {
|
||||
Op int `json:"op"` // Always 1
|
||||
Data voiceUDPD `json:"d"`
|
||||
}
|
||||
|
||||
// udpOpen opens a UDP connection to the voice server and completes the
|
||||
// initial required handshake. This connection is left open in the session
|
||||
// and can be used to send or receive audio. This should only be called
|
||||
// from voice.wsEvent OP2
|
||||
func (v *VoiceConnection) udpOpen() (err error) {
|
||||
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
|
||||
if v.wsConn == nil {
|
||||
return fmt.Errorf("nil voice websocket")
|
||||
}
|
||||
|
||||
if v.udpConn != nil {
|
||||
return fmt.Errorf("udp connection already open")
|
||||
}
|
||||
|
||||
if v.close == nil {
|
||||
return fmt.Errorf("nil close channel")
|
||||
}
|
||||
|
||||
if v.endpoint == "" {
|
||||
return fmt.Errorf("empty endpoint")
|
||||
}
|
||||
|
||||
host := v.op2.IP + ":" + strconv.Itoa(v.op2.Port)
|
||||
addr, err := net.ResolveUDPAddr("udp", host)
|
||||
if err != nil {
|
||||
v.log(LogWarning, "error resolving udp host %s, %s", host, err)
|
||||
return
|
||||
}
|
||||
|
||||
v.log(LogInformational, "connecting to udp addr %s", addr.String())
|
||||
v.udpConn, err = net.DialUDP("udp", nil, addr)
|
||||
if err != nil {
|
||||
v.log(LogWarning, "error connecting to udp addr %s, %s", addr.String(), err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a 70 byte array and put the SSRC code from the Op 2 VoiceConnection event
|
||||
// into it. Then send that over the UDP connection to Discord
|
||||
sb := make([]byte, 70)
|
||||
binary.BigEndian.PutUint32(sb, v.op2.SSRC)
|
||||
_, err = v.udpConn.Write(sb)
|
||||
if err != nil {
|
||||
v.log(LogWarning, "udp write error to %s, %s", addr.String(), err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a 70 byte array and listen for the initial handshake response
|
||||
// from Discord. Once we get it parse the IP and PORT information out
|
||||
// of the response. This should be our public IP and PORT as Discord
|
||||
// saw us.
|
||||
rb := make([]byte, 70)
|
||||
rlen, _, err := v.udpConn.ReadFromUDP(rb)
|
||||
if err != nil {
|
||||
v.log(LogWarning, "udp read error, %s, %s", addr.String(), err)
|
||||
return
|
||||
}
|
||||
|
||||
if rlen < 70 {
|
||||
v.log(LogWarning, "received udp packet too small")
|
||||
return fmt.Errorf("received udp packet too small")
|
||||
}
|
||||
|
||||
// Loop over position 4 through 20 to grab the IP address
|
||||
// Should never be beyond position 20.
|
||||
var ip string
|
||||
for i := 4; i < 20; i++ {
|
||||
if rb[i] == 0 {
|
||||
break
|
||||
}
|
||||
ip += string(rb[i])
|
||||
}
|
||||
|
||||
// Grab port from position 68 and 69
|
||||
port := binary.BigEndian.Uint16(rb[68:70])
|
||||
|
||||
// Take the data from above and send it back to Discord to finalize
|
||||
// the UDP connection handshake.
|
||||
data := voiceUDPOp{1, voiceUDPD{"udp", voiceUDPData{ip, port, "xsalsa20_poly1305"}}}
|
||||
|
||||
v.wsMutex.Lock()
|
||||
err = v.wsConn.WriteJSON(data)
|
||||
v.wsMutex.Unlock()
|
||||
if err != nil {
|
||||
v.log(LogWarning, "udp write error, %#v, %s", data, err)
|
||||
return
|
||||
}
|
||||
|
||||
// start udpKeepAlive
|
||||
go v.udpKeepAlive(v.udpConn, v.close, 5*time.Second)
|
||||
// TODO: find a way to check that it fired off okay
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// udpKeepAlive sends a udp packet to keep the udp connection open
|
||||
// This is still a bit of a "proof of concept"
|
||||
func (v *VoiceConnection) udpKeepAlive(udpConn *net.UDPConn, close <-chan struct{}, i time.Duration) {
|
||||
|
||||
if udpConn == nil || close == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
var sequence uint64
|
||||
|
||||
packet := make([]byte, 8)
|
||||
|
||||
ticker := time.NewTicker(i)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
|
||||
binary.LittleEndian.PutUint64(packet, sequence)
|
||||
sequence++
|
||||
|
||||
_, err = udpConn.Write(packet)
|
||||
if err != nil {
|
||||
v.log(LogError, "write error, %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// continue loop and send keepalive
|
||||
case <-close:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// opusSender will listen on the given channel and send any
|
||||
// pre-encoded opus audio to Discord. Supposedly.
|
||||
func (v *VoiceConnection) opusSender(udpConn *net.UDPConn, close <-chan struct{}, opus <-chan []byte, rate, size int) {
|
||||
|
||||
if udpConn == nil || close == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// VoiceConnection is now ready to receive audio packets
|
||||
// TODO: this needs reviewed as I think there must be a better way.
|
||||
v.Lock()
|
||||
v.Ready = true
|
||||
v.Unlock()
|
||||
defer func() {
|
||||
v.Lock()
|
||||
v.Ready = false
|
||||
v.Unlock()
|
||||
}()
|
||||
|
||||
var sequence uint16
|
||||
var timestamp uint32
|
||||
var recvbuf []byte
|
||||
var ok bool
|
||||
udpHeader := make([]byte, 12)
|
||||
var nonce [24]byte
|
||||
|
||||
// build the parts that don't change in the udpHeader
|
||||
udpHeader[0] = 0x80
|
||||
udpHeader[1] = 0x78
|
||||
binary.BigEndian.PutUint32(udpHeader[8:], v.op2.SSRC)
|
||||
|
||||
// start a send loop that loops until buf chan is closed
|
||||
ticker := time.NewTicker(time.Millisecond * time.Duration(size/(rate/1000)))
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
|
||||
// Get data from chan. If chan is closed, return.
|
||||
select {
|
||||
case <-close:
|
||||
return
|
||||
case recvbuf, ok = <-opus:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// else, continue loop
|
||||
}
|
||||
|
||||
v.RLock()
|
||||
speaking := v.speaking
|
||||
v.RUnlock()
|
||||
if !speaking {
|
||||
err := v.Speaking(true)
|
||||
if err != nil {
|
||||
v.log(LogError, "error sending speaking packet, %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Add sequence and timestamp to udpPacket
|
||||
binary.BigEndian.PutUint16(udpHeader[2:], sequence)
|
||||
binary.BigEndian.PutUint32(udpHeader[4:], timestamp)
|
||||
|
||||
// encrypt the opus data
|
||||
copy(nonce[:], udpHeader)
|
||||
v.RLock()
|
||||
sendbuf := secretbox.Seal(udpHeader, recvbuf, &nonce, &v.op4.SecretKey)
|
||||
v.RUnlock()
|
||||
|
||||
// block here until we're exactly at the right time :)
|
||||
// Then send rtp audio packet to Discord over UDP
|
||||
select {
|
||||
case <-close:
|
||||
return
|
||||
case <-ticker.C:
|
||||
// continue
|
||||
}
|
||||
_, err := udpConn.Write(sendbuf)
|
||||
|
||||
if err != nil {
|
||||
v.log(LogError, "udp write error, %s", err)
|
||||
v.log(LogDebug, "voice struct: %#v\n", v)
|
||||
return
|
||||
}
|
||||
|
||||
if (sequence) == 0xFFFF {
|
||||
sequence = 0
|
||||
} else {
|
||||
sequence++
|
||||
}
|
||||
|
||||
if (timestamp + uint32(size)) >= 0xFFFFFFFF {
|
||||
timestamp = 0
|
||||
} else {
|
||||
timestamp += uint32(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A Packet contains the headers and content of a received voice packet.
|
||||
type Packet struct {
|
||||
SSRC uint32
|
||||
Sequence uint16
|
||||
Timestamp uint32
|
||||
Type []byte
|
||||
Opus []byte
|
||||
PCM []int16
|
||||
}
|
||||
|
||||
// opusReceiver listens on the UDP socket for incoming packets
|
||||
// and sends them across the given channel
|
||||
// NOTE :: This function may change names later.
|
||||
func (v *VoiceConnection) opusReceiver(udpConn *net.UDPConn, close <-chan struct{}, c chan *Packet) {
|
||||
|
||||
if udpConn == nil || close == nil {
|
||||
return
|
||||
}
|
||||
|
||||
recvbuf := make([]byte, 1024)
|
||||
var nonce [24]byte
|
||||
|
||||
for {
|
||||
rlen, err := udpConn.Read(recvbuf)
|
||||
if err != nil {
|
||||
// Detect if we have been closed manually. If a Close() has already
|
||||
// happened, the udp connection we are listening on will be different
|
||||
// to the current session.
|
||||
v.RLock()
|
||||
sameConnection := v.udpConn == udpConn
|
||||
v.RUnlock()
|
||||
if sameConnection {
|
||||
|
||||
v.log(LogError, "udp read error, %s, %s", v.endpoint, err)
|
||||
v.log(LogDebug, "voice struct: %#v\n", v)
|
||||
|
||||
go v.reconnect()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case <-close:
|
||||
return
|
||||
default:
|
||||
// continue loop
|
||||
}
|
||||
|
||||
// For now, skip anything except audio.
|
||||
if rlen < 12 || (recvbuf[0] != 0x80 && recvbuf[0] != 0x90) {
|
||||
continue
|
||||
}
|
||||
|
||||
// build a audio packet struct
|
||||
p := Packet{}
|
||||
p.Type = recvbuf[0:2]
|
||||
p.Sequence = binary.BigEndian.Uint16(recvbuf[2:4])
|
||||
p.Timestamp = binary.BigEndian.Uint32(recvbuf[4:8])
|
||||
p.SSRC = binary.BigEndian.Uint32(recvbuf[8:12])
|
||||
// decrypt opus data
|
||||
copy(nonce[:], recvbuf[0:12])
|
||||
p.Opus, _ = secretbox.Open(nil, recvbuf[12:rlen], &nonce, &v.op4.SecretKey)
|
||||
|
||||
if len(p.Opus) > 8 && recvbuf[0] == 0x90 {
|
||||
// Extension bit is set, first 8 bytes is the extended header
|
||||
p.Opus = p.Opus[8:]
|
||||
}
|
||||
|
||||
if c != nil {
|
||||
select {
|
||||
case c <- &p:
|
||||
case <-close:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reconnect will close down a voice connection then immediately try to
|
||||
// reconnect to that session.
|
||||
// NOTE : This func is messy and a WIP while I find what works.
|
||||
// It will be cleaned up once a proven stable option is flushed out.
|
||||
// aka: this is ugly shit code, please don't judge too harshly.
|
||||
func (v *VoiceConnection) reconnect() {
|
||||
|
||||
v.log(LogInformational, "called")
|
||||
|
||||
v.Lock()
|
||||
if v.reconnecting {
|
||||
v.log(LogInformational, "already reconnecting to channel %s, exiting", v.ChannelID)
|
||||
v.Unlock()
|
||||
return
|
||||
}
|
||||
v.reconnecting = true
|
||||
v.Unlock()
|
||||
|
||||
defer func() { v.reconnecting = false }()
|
||||
|
||||
// Close any currently open connections
|
||||
v.Close()
|
||||
|
||||
wait := time.Duration(1)
|
||||
for {
|
||||
|
||||
<-time.After(wait * time.Second)
|
||||
wait *= 2
|
||||
if wait > 600 {
|
||||
wait = 600
|
||||
}
|
||||
|
||||
if v.session.DataReady == false || v.session.wsConn == nil {
|
||||
v.log(LogInformational, "cannot reconnect to channel %s with unready session", v.ChannelID)
|
||||
continue
|
||||
}
|
||||
|
||||
v.log(LogInformational, "trying to reconnect to channel %s", v.ChannelID)
|
||||
|
||||
_, err := v.session.ChannelVoiceJoin(v.GuildID, v.ChannelID, v.mute, v.deaf)
|
||||
if err == nil {
|
||||
v.log(LogInformational, "successfully reconnected to channel %s", v.ChannelID)
|
||||
return
|
||||
}
|
||||
|
||||
v.log(LogInformational, "error reconnecting to channel %s, %s", v.ChannelID, err)
|
||||
|
||||
// if the reconnect above didn't work lets just send a disconnect
|
||||
// packet to reset things.
|
||||
// Send a OP4 with a nil channel to disconnect
|
||||
data := voiceChannelJoinOp{4, voiceChannelJoinData{&v.GuildID, nil, true, true}}
|
||||
v.session.wsMutex.Lock()
|
||||
err = v.session.wsConn.WriteJSON(data)
|
||||
v.session.wsMutex.Unlock()
|
||||
if err != nil {
|
||||
v.log(LogError, "error sending disconnect packet, %s", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,903 @@
|
|||
// Discordgo - Discord bindings for Go
|
||||
// Available at https://github.com/Bios-Marcel/discordgo
|
||||
|
||||
// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file contains low level functions for interacting with the Discord
|
||||
// data websocket interface.
|
||||
|
||||
package discordgo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/zlib"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// ErrWSAlreadyOpen is thrown when you attempt to open
|
||||
// a websocket that already is open.
|
||||
var ErrWSAlreadyOpen = errors.New("web socket already opened")
|
||||
|
||||
// ErrWSNotFound is thrown when you attempt to use a websocket
|
||||
// that doesn't exist
|
||||
var ErrWSNotFound = errors.New("no websocket connection exists")
|
||||
|
||||
// ErrWSShardBounds is thrown when you try to use a shard ID that is
|
||||
// less than the total shard count
|
||||
var ErrWSShardBounds = errors.New("ShardID must be less than ShardCount")
|
||||
|
||||
type resumePacket struct {
|
||||
Op int `json:"op"`
|
||||
Data struct {
|
||||
Token string `json:"token"`
|
||||
SessionID string `json:"session_id"`
|
||||
Sequence int64 `json:"seq"`
|
||||
} `json:"d"`
|
||||
}
|
||||
|
||||
// Open creates a websocket connection to Discord.
|
||||
// See: https://discord.com/developers/docs/topics/gateway#connecting
|
||||
func (s *Session) Open() error {
|
||||
s.log(LogInformational, "called")
|
||||
|
||||
var err error
|
||||
|
||||
// Prevent Open or other major Session functions from
|
||||
// being called while Open is still running.
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
// If the websock is already open, bail out here.
|
||||
if s.wsConn != nil {
|
||||
return ErrWSAlreadyOpen
|
||||
}
|
||||
|
||||
// Get the gateway to use for the Websocket connection
|
||||
if s.gateway == "" {
|
||||
s.gateway, err = s.Gateway()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add the version and encoding to the URL
|
||||
s.gateway = s.gateway + "?v=" + APIVersion + "&encoding=json"
|
||||
}
|
||||
|
||||
// Connect to the Gateway
|
||||
s.log(LogInformational, "connecting to gateway %s", s.gateway)
|
||||
header := http.Header{}
|
||||
header.Add("accept-encoding", "zlib")
|
||||
s.wsConn, _, err = websocket.DefaultDialer.Dial(s.gateway, header)
|
||||
if err != nil {
|
||||
s.log(LogError, "error connecting to gateway %s, %s", s.gateway, err)
|
||||
s.gateway = "" // clear cached gateway
|
||||
s.wsConn = nil // Just to be safe.
|
||||
return err
|
||||
}
|
||||
|
||||
s.wsConn.SetCloseHandler(func(code int, text string) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
defer func() {
|
||||
// because of this, all code below must set err to the error
|
||||
// when exiting with an error :) Maybe someone has a better
|
||||
// way :)
|
||||
if err != nil {
|
||||
s.wsConn.Close()
|
||||
s.wsConn = nil
|
||||
}
|
||||
}()
|
||||
|
||||
// The first response from Discord should be an Op 10 (Hello) Packet.
|
||||
// When processed by onEvent the heartbeat goroutine will be started.
|
||||
mt, m, err := s.wsConn.ReadMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e, err := s.onEvent(mt, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Operation != 10 {
|
||||
err = fmt.Errorf("expecting Op 10, got Op %d instead", e.Operation)
|
||||
return err
|
||||
}
|
||||
s.log(LogInformational, "Op 10 Hello Packet received from Discord")
|
||||
s.LastHeartbeatAck = time.Now().UTC()
|
||||
var h helloOp
|
||||
if err = json.Unmarshal(e.RawData, &h); err != nil {
|
||||
err = fmt.Errorf("error unmarshalling helloOp, %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Now we send either an Op 2 Identity if this is a brand new
|
||||
// connection or Op 6 Resume if we are resuming an existing connection.
|
||||
sequence := atomic.LoadInt64(s.sequence)
|
||||
if s.sessionID == "" && sequence == 0 {
|
||||
|
||||
// Send Op 2 Identity Packet
|
||||
err = s.identify()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error sending identify packet to gateway, %s, %s", s.gateway, err)
|
||||
return err
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Send Op 6 Resume Packet
|
||||
p := resumePacket{}
|
||||
p.Op = 6
|
||||
p.Data.Token = s.Token
|
||||
p.Data.SessionID = s.sessionID
|
||||
p.Data.Sequence = sequence
|
||||
|
||||
s.log(LogInformational, "sending resume packet to gateway")
|
||||
s.wsMutex.Lock()
|
||||
err = s.wsConn.WriteJSON(p)
|
||||
s.wsMutex.Unlock()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error sending gateway resume packet, %s, %s", s.gateway, err)
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// A basic state is a hard requirement for Voice.
|
||||
// We create it here so the below READY/RESUMED packet can populate
|
||||
// the state :)
|
||||
// XXX: Move to New() func?
|
||||
if s.State == nil {
|
||||
state := NewState()
|
||||
state.TrackChannels = false
|
||||
state.TrackEmojis = false
|
||||
state.TrackMembers = false
|
||||
state.TrackRoles = false
|
||||
state.TrackVoice = false
|
||||
s.State = state
|
||||
}
|
||||
|
||||
// Now Discord should send us a READY or RESUMED packet.
|
||||
mt, m, err = s.wsConn.ReadMessage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
e, err = s.onEvent(mt, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if e.Type != `READY` && e.Type != `RESUMED` {
|
||||
// This is not fatal, but it does not follow their API documentation.
|
||||
s.log(LogWarning, "Expected READY/RESUMED, instead got:\n%#v\n", e)
|
||||
}
|
||||
s.log(LogInformational, "First Packet:\n%#v\n", e)
|
||||
|
||||
s.log(LogInformational, "We are now connected to Discord, emitting connect event")
|
||||
s.handleEvent(connectEventType, &Connect{})
|
||||
|
||||
// A VoiceConnections map is a hard requirement for Voice.
|
||||
// XXX: can this be moved to when opening a voice connection?
|
||||
if s.VoiceConnections == nil {
|
||||
s.log(LogInformational, "creating new VoiceConnections map")
|
||||
s.VoiceConnections = make(map[string]*VoiceConnection)
|
||||
}
|
||||
|
||||
// Create listening chan outside of listen, as it needs to happen inside the
|
||||
// mutex lock and needs to exist before calling heartbeat and listen
|
||||
// go rountines.
|
||||
s.listening = make(chan interface{})
|
||||
|
||||
// Start sending heartbeats and reading messages from Discord.
|
||||
go s.heartbeat(s.wsConn, s.listening, h.HeartbeatInterval)
|
||||
go s.listen(s.wsConn, s.listening)
|
||||
|
||||
s.log(LogInformational, "exiting")
|
||||
return nil
|
||||
}
|
||||
|
||||
// listen polls the websocket connection for events, it will stop when the
|
||||
// listening channel is closed, or an error occurs.
|
||||
func (s *Session) listen(wsConn *websocket.Conn, listening <-chan interface{}) {
|
||||
|
||||
s.log(LogInformational, "called")
|
||||
|
||||
for {
|
||||
|
||||
messageType, message, err := wsConn.ReadMessage()
|
||||
|
||||
if err != nil {
|
||||
|
||||
// Detect if we have been closed manually. If a Close() has already
|
||||
// happened, the websocket we are listening on will be different to
|
||||
// the current session.
|
||||
s.RLock()
|
||||
sameConnection := s.wsConn == wsConn
|
||||
s.RUnlock()
|
||||
|
||||
if sameConnection {
|
||||
|
||||
s.log(LogWarning, "error reading from gateway %s websocket, %s", s.gateway, err)
|
||||
// There has been an error reading, close the websocket so that
|
||||
// OnDisconnect event is emitted.
|
||||
err := s.Close()
|
||||
if err != nil {
|
||||
s.log(LogWarning, "error closing session connection, %s", err)
|
||||
}
|
||||
|
||||
s.log(LogInformational, "calling reconnect() now")
|
||||
s.reconnect()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
|
||||
case <-listening:
|
||||
return
|
||||
|
||||
default:
|
||||
s.onEvent(messageType, message)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type heartbeatOp struct {
|
||||
Op int `json:"op"`
|
||||
Data int64 `json:"d"`
|
||||
}
|
||||
|
||||
type helloOp struct {
|
||||
HeartbeatInterval time.Duration `json:"heartbeat_interval"`
|
||||
}
|
||||
|
||||
// FailedHeartbeatAcks is the Number of heartbeat intervals to wait until forcing a connection restart.
|
||||
const FailedHeartbeatAcks time.Duration = 5 * time.Millisecond
|
||||
|
||||
// HeartbeatLatency returns the latency between heartbeat acknowledgement and heartbeat send.
|
||||
func (s *Session) HeartbeatLatency() time.Duration {
|
||||
|
||||
return s.LastHeartbeatAck.Sub(s.LastHeartbeatSent)
|
||||
|
||||
}
|
||||
|
||||
// heartbeat sends regular heartbeats to Discord so it knows the client
|
||||
// is still connected. If you do not send these heartbeats Discord will
|
||||
// disconnect the websocket connection after a few seconds.
|
||||
func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}, heartbeatIntervalMsec time.Duration) {
|
||||
|
||||
s.log(LogInformational, "called")
|
||||
|
||||
if listening == nil || wsConn == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
ticker := time.NewTicker(heartbeatIntervalMsec * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
s.RLock()
|
||||
last := s.LastHeartbeatAck
|
||||
s.RUnlock()
|
||||
sequence := atomic.LoadInt64(s.sequence)
|
||||
s.log(LogDebug, "sending gateway websocket heartbeat seq %d", sequence)
|
||||
s.wsMutex.Lock()
|
||||
s.LastHeartbeatSent = time.Now().UTC()
|
||||
err = wsConn.WriteJSON(heartbeatOp{1, sequence})
|
||||
s.wsMutex.Unlock()
|
||||
if err != nil || time.Now().UTC().Sub(last) > (heartbeatIntervalMsec*FailedHeartbeatAcks) {
|
||||
if err != nil {
|
||||
s.log(LogError, "error sending heartbeat to gateway %s, %s", s.gateway, err)
|
||||
} else {
|
||||
s.log(LogError, "haven't gotten a heartbeat ACK in %v, triggering a reconnection", time.Now().UTC().Sub(last))
|
||||
}
|
||||
s.Close()
|
||||
s.reconnect()
|
||||
return
|
||||
}
|
||||
s.Lock()
|
||||
s.DataReady = true
|
||||
s.Unlock()
|
||||
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// continue loop and send heartbeat
|
||||
case <-listening:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateStatusData ia provided to UpdateStatusComplex()
|
||||
type UpdateStatusData struct {
|
||||
IdleSince *int `json:"since"`
|
||||
Game *Game `json:"game"`
|
||||
AFK bool `json:"afk"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type updateStatusOp struct {
|
||||
Op int `json:"op"`
|
||||
Data UpdateStatusData `json:"d"`
|
||||
}
|
||||
|
||||
func newUpdateStatusData(idle int, gameType GameType, game, url string) *UpdateStatusData {
|
||||
usd := &UpdateStatusData{
|
||||
Status: "online",
|
||||
}
|
||||
|
||||
if idle > 0 {
|
||||
usd.IdleSince = &idle
|
||||
}
|
||||
|
||||
if game != "" {
|
||||
usd.Game = &Game{
|
||||
Name: game,
|
||||
Type: gameType,
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
return usd
|
||||
}
|
||||
|
||||
// UpdateStatus is used to update the user's status.
|
||||
// If idle>0 then set status to idle.
|
||||
// If game!="" then set game.
|
||||
// if otherwise, set status to active, and no game.
|
||||
func (s *Session) UpdateStatus(idle int, game string) (err error) {
|
||||
return s.UpdateStatusComplex(*newUpdateStatusData(idle, GameTypeGame, game, ""))
|
||||
}
|
||||
|
||||
// UpdateStreamingStatus is used to update the user's streaming status.
|
||||
// If idle>0 then set status to idle.
|
||||
// If game!="" then set game.
|
||||
// If game!="" and url!="" then set the status type to streaming with the URL set.
|
||||
// if otherwise, set status to active, and no game.
|
||||
func (s *Session) UpdateStreamingStatus(idle int, game string, url string) (err error) {
|
||||
gameType := GameTypeGame
|
||||
if url != "" {
|
||||
gameType = GameTypeStreaming
|
||||
}
|
||||
return s.UpdateStatusComplex(*newUpdateStatusData(idle, gameType, game, url))
|
||||
}
|
||||
|
||||
// UpdateListeningStatus is used to set the user to "Listening to..."
|
||||
// If game!="" then set to what user is listening to
|
||||
// Else, set user to active and no game.
|
||||
func (s *Session) UpdateListeningStatus(game string) (err error) {
|
||||
return s.UpdateStatusComplex(*newUpdateStatusData(0, GameTypeListening, game, ""))
|
||||
}
|
||||
|
||||
// UpdateStatusComplex allows for sending the raw status update data untouched by discordgo.
|
||||
func (s *Session) UpdateStatusComplex(usd UpdateStatusData) (err error) {
|
||||
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
if s.wsConn == nil {
|
||||
return ErrWSNotFound
|
||||
}
|
||||
|
||||
s.wsMutex.Lock()
|
||||
err = s.wsConn.WriteJSON(updateStatusOp{3, usd})
|
||||
s.wsMutex.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type requestGuildMembersData struct {
|
||||
GuildIDs []string `json:"guild_id"`
|
||||
Query string `json:"query"`
|
||||
Limit int `json:"limit"`
|
||||
Presences bool `json:"presences"`
|
||||
}
|
||||
|
||||
type requestGuildMembersOp struct {
|
||||
Op int `json:"op"`
|
||||
Data requestGuildMembersData `json:"d"`
|
||||
}
|
||||
|
||||
// RequestGuildMembers requests guild members from the gateway
|
||||
// The gateway responds with GuildMembersChunk events
|
||||
// guildID : Single Guild ID to request members of
|
||||
// query : String that username starts with, leave empty to return all members
|
||||
// limit : Max number of items to return, or 0 to request all members matched
|
||||
// presences : Whether to request presences of guild members
|
||||
func (s *Session) RequestGuildMembers(guildID string, query string, limit int, presences bool) (err error) {
|
||||
data := requestGuildMembersData{
|
||||
GuildIDs: []string{guildID},
|
||||
Query: query,
|
||||
Limit: limit,
|
||||
Presences: presences,
|
||||
}
|
||||
err = s.requestGuildMembers(data)
|
||||
return
|
||||
}
|
||||
|
||||
// RequestGuildMembersBatch requests guild members from the gateway
|
||||
// The gateway responds with GuildMembersChunk events
|
||||
// guildID : Slice of guild IDs to request members of
|
||||
// query : String that username starts with, leave empty to return all members
|
||||
// limit : Max number of items to return, or 0 to request all members matched
|
||||
// presences : Whether to request presences of guild members
|
||||
func (s *Session) RequestGuildMembersBatch(guildIDs []string, query string, limit int, presences bool) (err error) {
|
||||
data := requestGuildMembersData{
|
||||
GuildIDs: guildIDs,
|
||||
Query: query,
|
||||
Limit: limit,
|
||||
Presences: presences,
|
||||
}
|
||||
err = s.requestGuildMembers(data)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *Session) requestGuildMembers(data requestGuildMembersData) (err error) {
|
||||
s.log(LogInformational, "called")
|
||||
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
if s.wsConn == nil {
|
||||
return ErrWSNotFound
|
||||
}
|
||||
|
||||
s.wsMutex.Lock()
|
||||
err = s.wsConn.WriteJSON(requestGuildMembersOp{8, data})
|
||||
s.wsMutex.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// onEvent is the "event handler" for all messages received on the
|
||||
// Discord Gateway API websocket connection.
|
||||
//
|
||||
// If you use the AddHandler() function to register a handler for a
|
||||
// specific event this function will pass the event along to that handler.
|
||||
//
|
||||
// If you use the AddHandler() function to register a handler for the
|
||||
// "OnEvent" event then all events will be passed to that handler.
|
||||
func (s *Session) onEvent(messageType int, message []byte) (*Event, error) {
|
||||
|
||||
var err error
|
||||
var reader io.Reader
|
||||
reader = bytes.NewBuffer(message)
|
||||
|
||||
// If this is a compressed message, uncompress it.
|
||||
if messageType == websocket.BinaryMessage {
|
||||
|
||||
z, err2 := zlib.NewReader(reader)
|
||||
if err2 != nil {
|
||||
s.log(LogError, "error uncompressing websocket message, %s", err)
|
||||
return nil, err2
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err3 := z.Close()
|
||||
if err3 != nil {
|
||||
s.log(LogWarning, "error closing zlib, %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
reader = z
|
||||
}
|
||||
|
||||
// Decode the event into an Event struct.
|
||||
var e *Event
|
||||
decoder := json.NewDecoder(reader)
|
||||
if err = decoder.Decode(&e); err != nil {
|
||||
s.log(LogError, "error decoding websocket message, %s", err)
|
||||
return e, err
|
||||
}
|
||||
|
||||
s.log(LogDebug, "Op: %d, Seq: %d, Type: %s, Data: %s\n\n", e.Operation, e.Sequence, e.Type, string(e.RawData))
|
||||
|
||||
// Ping request.
|
||||
// Must respond with a heartbeat packet within 5 seconds
|
||||
if e.Operation == 1 {
|
||||
s.log(LogInformational, "sending heartbeat in response to Op1")
|
||||
s.wsMutex.Lock()
|
||||
err = s.wsConn.WriteJSON(heartbeatOp{1, atomic.LoadInt64(s.sequence)})
|
||||
s.wsMutex.Unlock()
|
||||
if err != nil {
|
||||
s.log(LogError, "error sending heartbeat in response to Op1")
|
||||
return e, err
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// Reconnect
|
||||
// Must immediately disconnect from gateway and reconnect to new gateway.
|
||||
if e.Operation == 7 {
|
||||
s.log(LogInformational, "Closing and reconnecting in response to Op7")
|
||||
s.CloseWithCode(websocket.CloseServiceRestart)
|
||||
s.reconnect()
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// Invalid Session
|
||||
// Must respond with a Identify packet.
|
||||
if e.Operation == 9 {
|
||||
|
||||
s.log(LogInformational, "sending identify packet to gateway in response to Op9")
|
||||
|
||||
err = s.identify()
|
||||
if err != nil {
|
||||
s.log(LogWarning, "error sending gateway identify packet, %s, %s", s.gateway, err)
|
||||
return e, err
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
if e.Operation == 10 {
|
||||
// Op10 is handled by Open()
|
||||
return e, nil
|
||||
}
|
||||
|
||||
if e.Operation == 11 {
|
||||
s.Lock()
|
||||
s.LastHeartbeatAck = time.Now().UTC()
|
||||
s.Unlock()
|
||||
s.log(LogDebug, "got heartbeat ACK")
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// Do not try to Dispatch a non-Dispatch Message
|
||||
if e.Operation != 0 {
|
||||
// But we probably should be doing something with them.
|
||||
// TEMP
|
||||
s.log(LogWarning, "unknown Op: %d, Seq: %d, Type: %s, Data: %s, message: %s", e.Operation, e.Sequence, e.Type, string(e.RawData), string(message))
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// Store the message sequence
|
||||
atomic.StoreInt64(s.sequence, e.Sequence)
|
||||
|
||||
// Map event to registered event handlers and pass it along to any registered handlers.
|
||||
if eh, ok := registeredInterfaceProviders[e.Type]; ok {
|
||||
e.Struct = eh.New()
|
||||
|
||||
// Attempt to unmarshal our event.
|
||||
if err = json.Unmarshal(e.RawData, e.Struct); err != nil {
|
||||
//fmt.Println((string)(e.RawData))
|
||||
//os.Exit(0)
|
||||
s.log(LogError, "error unmarshalling %s event, %s", e.Type, err)
|
||||
}
|
||||
|
||||
// Send event to any registered event handlers for it's type.
|
||||
// Because the above doesn't cancel this, in case of an error
|
||||
// the struct could be partially populated or at default values.
|
||||
// However, most errors are due to a single field and I feel
|
||||
// it's better to pass along what we received than nothing at all.
|
||||
// TODO: Think about that decision :)
|
||||
// Either way, READY events must fire, even with errors.
|
||||
s.handleEvent(e.Type, e.Struct)
|
||||
} else {
|
||||
s.log(LogWarning, "unknown event: Op: %d, Seq: %d, Type: %s, Data: %s", e.Operation, e.Sequence, e.Type, string(e.RawData))
|
||||
}
|
||||
|
||||
// For legacy reasons, we send the raw event also, this could be useful for handling unknown events.
|
||||
s.handleEvent(eventEventType, e)
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Code related to voice connections that initiate over the data websocket
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
type voiceChannelJoinData struct {
|
||||
GuildID *string `json:"guild_id"`
|
||||
ChannelID *string `json:"channel_id"`
|
||||
SelfMute bool `json:"self_mute"`
|
||||
SelfDeaf bool `json:"self_deaf"`
|
||||
}
|
||||
|
||||
type voiceChannelJoinOp struct {
|
||||
Op int `json:"op"`
|
||||
Data voiceChannelJoinData `json:"d"`
|
||||
}
|
||||
|
||||
// ChannelVoiceJoin joins the session user to a voice channel.
|
||||
//
|
||||
// gID : Guild ID of the channel to join.
|
||||
// cID : Channel ID of the channel to join.
|
||||
// mute : If true, you will be set to muted upon joining.
|
||||
// deaf : If true, you will be set to deafened upon joining.
|
||||
func (s *Session) ChannelVoiceJoin(gID, cID string, mute, deaf bool) (voice *VoiceConnection, err error) {
|
||||
|
||||
s.log(LogInformational, "called")
|
||||
|
||||
s.RLock()
|
||||
voice, _ = s.VoiceConnections[gID]
|
||||
s.RUnlock()
|
||||
|
||||
if voice == nil {
|
||||
voice = &VoiceConnection{}
|
||||
s.Lock()
|
||||
s.VoiceConnections[gID] = voice
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
voice.Lock()
|
||||
voice.GuildID = gID
|
||||
voice.ChannelID = cID
|
||||
voice.deaf = deaf
|
||||
voice.mute = mute
|
||||
voice.session = s
|
||||
voice.Unlock()
|
||||
|
||||
err = s.ChannelVoiceJoinManual(gID, cID, mute, deaf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// doesn't exactly work perfect yet.. TODO
|
||||
err = voice.waitUntilConnected()
|
||||
if err != nil {
|
||||
s.log(LogWarning, "error waiting for voice to connect, %s", err)
|
||||
voice.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ChannelVoiceJoinManual initiates a voice session to a voice channel, but does not complete it.
|
||||
//
|
||||
// This should only be used when the VoiceServerUpdate will be intercepted and used elsewhere.
|
||||
//
|
||||
// gID : Guild ID of the channel to join.
|
||||
// cID : Channel ID of the channel to join, leave empty to disconnect.
|
||||
// mute : If true, you will be set to muted upon joining.
|
||||
// deaf : If true, you will be set to deafened upon joining.
|
||||
func (s *Session) ChannelVoiceJoinManual(gID, cID string, mute, deaf bool) (err error) {
|
||||
|
||||
s.log(LogInformational, "called")
|
||||
|
||||
var channelID *string
|
||||
if cID == "" {
|
||||
channelID = nil
|
||||
} else {
|
||||
channelID = &cID
|
||||
}
|
||||
|
||||
// Send the request to Discord that we want to join the voice channel
|
||||
data := voiceChannelJoinOp{4, voiceChannelJoinData{&gID, channelID, mute, deaf}}
|
||||
s.wsMutex.Lock()
|
||||
err = s.wsConn.WriteJSON(data)
|
||||
s.wsMutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// onVoiceStateUpdate handles Voice State Update events on the data websocket.
|
||||
func (s *Session) onVoiceStateUpdate(st *VoiceStateUpdate) {
|
||||
|
||||
// If we don't have a connection for the channel, don't bother
|
||||
if st.ChannelID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if we have a voice connection to update
|
||||
s.RLock()
|
||||
voice, exists := s.VoiceConnections[st.GuildID]
|
||||
s.RUnlock()
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
// We only care about events that are about us.
|
||||
if s.State.User.ID != st.UserID {
|
||||
return
|
||||
}
|
||||
|
||||
// Store the SessionID for later use.
|
||||
voice.Lock()
|
||||
voice.UserID = st.UserID
|
||||
voice.sessionID = st.SessionID
|
||||
voice.ChannelID = st.ChannelID
|
||||
voice.Unlock()
|
||||
}
|
||||
|
||||
// onVoiceServerUpdate handles the Voice Server Update data websocket event.
|
||||
//
|
||||
// This is also fired if the Guild's voice region changes while connected
|
||||
// to a voice channel. In that case, need to re-establish connection to
|
||||
// the new region endpoint.
|
||||
func (s *Session) onVoiceServerUpdate(st *VoiceServerUpdate) {
|
||||
|
||||
s.log(LogInformational, "called")
|
||||
|
||||
s.RLock()
|
||||
voice, exists := s.VoiceConnections[st.GuildID]
|
||||
s.RUnlock()
|
||||
|
||||
// If no VoiceConnection exists, just skip this
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
|
||||
// If currently connected to voice ws/udp, then disconnect.
|
||||
// Has no effect if not connected.
|
||||
voice.Close()
|
||||
|
||||
// Store values for later use
|
||||
voice.Lock()
|
||||
voice.token = st.Token
|
||||
voice.endpoint = st.Endpoint
|
||||
voice.GuildID = st.GuildID
|
||||
voice.Unlock()
|
||||
|
||||
// Open a connection to the voice server
|
||||
err := voice.open()
|
||||
if err != nil {
|
||||
s.log(LogError, "onVoiceServerUpdate voice.open, %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
type identifyOp struct {
|
||||
Op int `json:"op"`
|
||||
Data Identify `json:"d"`
|
||||
}
|
||||
|
||||
// identify sends the identify packet to the gateway
|
||||
func (s *Session) identify() error {
|
||||
s.log(LogDebug, "called")
|
||||
|
||||
// TODO: This is a temporary block of code to help
|
||||
// maintain backwards compatability
|
||||
if s.Compress == false {
|
||||
s.Identify.Compress = false
|
||||
}
|
||||
|
||||
// TODO: This is a temporary block of code to help
|
||||
// maintain backwards compatability
|
||||
if s.Token != "" && s.Identify.Token == "" {
|
||||
s.Identify.Token = s.Token
|
||||
}
|
||||
|
||||
// TODO: Below block should be refactored so ShardID and ShardCount
|
||||
// can be deprecated and their usage moved to the Session.Identify
|
||||
// struct
|
||||
if s.ShardCount > 1 {
|
||||
|
||||
if s.ShardID >= s.ShardCount {
|
||||
return ErrWSShardBounds
|
||||
}
|
||||
|
||||
s.Identify.Shard = &[2]int{s.ShardID, s.ShardCount}
|
||||
}
|
||||
|
||||
// Send Identify packet to Discord
|
||||
op := identifyOp{2, s.Identify}
|
||||
s.log(LogDebug, "Identify Packet: \n%#v", op)
|
||||
s.wsMutex.Lock()
|
||||
err := s.wsConn.WriteJSON(op)
|
||||
s.wsMutex.Unlock()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Session) reconnect() {
|
||||
|
||||
s.log(LogInformational, "called")
|
||||
|
||||
var err error
|
||||
|
||||
if s.ShouldReconnectOnError {
|
||||
|
||||
wait := time.Duration(1)
|
||||
|
||||
for {
|
||||
s.log(LogInformational, "trying to reconnect to gateway")
|
||||
|
||||
err = s.Open()
|
||||
if err == nil {
|
||||
s.log(LogInformational, "successfully reconnected to gateway")
|
||||
|
||||
// I'm not sure if this is actually needed.
|
||||
// if the gw reconnect works properly, voice should stay alive
|
||||
// However, there seems to be cases where something "weird"
|
||||
// happens. So we're doing this for now just to improve
|
||||
// stability in those edge cases.
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
for _, v := range s.VoiceConnections {
|
||||
|
||||
s.log(LogInformational, "reconnecting voice connection to guild %s", v.GuildID)
|
||||
go v.reconnect()
|
||||
|
||||
// This is here just to prevent violently spamming the
|
||||
// voice reconnects
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Certain race conditions can call reconnect() twice. If this happens, we
|
||||
// just break out of the reconnect loop
|
||||
if err == ErrWSAlreadyOpen {
|
||||
s.log(LogInformational, "Websocket already exists, no need to reconnect")
|
||||
return
|
||||
}
|
||||
|
||||
s.log(LogError, "error reconnecting to gateway, %s", err)
|
||||
|
||||
<-time.After(wait * time.Second)
|
||||
wait *= 2
|
||||
if wait > 600 {
|
||||
wait = 600
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes a websocket and stops all listening/heartbeat goroutines.
|
||||
// TODO: Add support for Voice WS/UDP
|
||||
func (s *Session) Close() error {
|
||||
return s.CloseWithCode(websocket.CloseNormalClosure)
|
||||
}
|
||||
|
||||
// CloseWithCode closes a websocket using the provided closeCode and stops all
|
||||
// listening/heartbeat goroutines.
|
||||
// TODO: Add support for Voice WS/UDP connections
|
||||
func (s *Session) CloseWithCode(closeCode int) (err error) {
|
||||
|
||||
s.log(LogInformational, "called")
|
||||
s.Lock()
|
||||
|
||||
s.DataReady = false
|
||||
|
||||
if s.listening != nil {
|
||||
s.log(LogInformational, "closing listening channel")
|
||||
close(s.listening)
|
||||
s.listening = nil
|
||||
}
|
||||
|
||||
// TODO: Close all active Voice Connections too
|
||||
// this should force stop any reconnecting voice channels too
|
||||
|
||||
if s.wsConn != nil {
|
||||
|
||||
s.log(LogInformational, "sending close frame")
|
||||
// To cleanly close a connection, a client should send a close
|
||||
// frame and wait for the server to close the connection.
|
||||
s.wsMutex.Lock()
|
||||
err := s.wsConn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(closeCode, ""))
|
||||
s.wsMutex.Unlock()
|
||||
if err != nil {
|
||||
s.log(LogInformational, "error closing websocket, %s", err)
|
||||
}
|
||||
|
||||
// TODO: Wait for Discord to actually close the connection.
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
s.log(LogInformational, "closing gateway websocket")
|
||||
err = s.wsConn.Close()
|
||||
if err != nil {
|
||||
s.log(LogInformational, "error closing websocket, %s", err)
|
||||
}
|
||||
|
||||
s.wsConn = nil
|
||||
}
|
||||
|
||||
s.Unlock()
|
||||
|
||||
s.log(LogInformational, "emit disconnect event")
|
||||
s.handleEvent(disconnectEventType, &Disconnect{})
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
os: osx
|
||||
language: go
|
||||
go:
|
||||
- "1.10"
|
|
@ -0,0 +1,29 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2019, Marcel Schramm
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,28 @@
|
|||
# goclipimg
|
||||
|
||||
| OS | Build |
|
||||
| - | - |
|
||||
| linux | [![builds.sr.ht status](https://builds.sr.ht/~biosmarcel/goclipimg/arch.yml.svg)](https://builds.sr.ht/~biosmarcel/goclipimg/arch.yml?) |
|
||||
| darwin | [![Build Status](https://travis-ci.org/Bios-Marcel/goclipimg.svg?branch=master)](https://travis-ci.org/Bios-Marcel/goclipimg) |
|
||||
| windows | [![Build status](https://ci.appveyor.com/api/projects/status/jk8g0q27hle7m98p/branch/master?svg=true)](https://ci.appveyor.com/project/Bios-Marcel/goclipimg/branch/master) |
|
||||
|
||||
This is just a tiny library that helps you getting an image from your clipboard into your application.
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirements - Linux
|
||||
|
||||
If you are running x11 you'll need to have `xclip` installed.
|
||||
|
||||
On Wayland you need `wl-clipboard`.
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
func main() {
|
||||
data, readError := goclipimg.GetImageFromClipboard()
|
||||
if readError == nil {
|
||||
doSomethingWithYourPNG(data)
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,14 @@
|
|||
build: off
|
||||
|
||||
clone_folder: c:\gopath\src\github.com\Bios-Marcel\goclipimg
|
||||
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
|
||||
stack: go 1.12
|
||||
|
||||
before_test:
|
||||
- go vet ./...
|
||||
|
||||
test_script:
|
||||
- go test ./...
|
|
@ -0,0 +1,38 @@
|
|||
package goclipimg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
pngHeader = []byte{
|
||||
/* Has the high bit set to detect transmission systems that do not
|
||||
support 8 bit data and to reduce the chance that a text file i
|
||||
mistakenly interpreted as a PNG, or vice versa. */
|
||||
0x89,
|
||||
/* In ASCII, the letters PNG, allowing a person to identify the
|
||||
format easily if it is viewed in a text editor. */
|
||||
0x50, 0x4E, 0x47}
|
||||
// ErrNoImageInClipboard means that no data was returned.
|
||||
ErrNoImageInClipboard = errors.New("the clipboard doesn't contain an image")
|
||||
)
|
||||
|
||||
// GetImageFromClipboard returns either a byte array containing PNG data or an
|
||||
// error that indicates that no png could be retrieved.
|
||||
func GetImageFromClipboard() ([]byte, error) {
|
||||
data, err := getImageFromClipboard()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) < 8 {
|
||||
return nil, ErrNoImageInClipboard
|
||||
}
|
||||
|
||||
if bytes.Compare(data[:4], pngHeader[:]) != 0 {
|
||||
return nil, ErrNoImageInClipboard
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package goclipimg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
const imageToFile = "tell application \"System Events\" to write (the clipboard as «class PNGf») to \"%s\""
|
||||
|
||||
func getImageFromClipboard() ([]byte, error) {
|
||||
tempFile, tempFileError := ioutil.TempFile("", "clipimg")
|
||||
if tempFileError != nil {
|
||||
return nil, tempFileError
|
||||
}
|
||||
|
||||
imagePath := tempFile.Name()
|
||||
defer os.Remove(imagePath)
|
||||
|
||||
command := exec.Command("osascript", "-e", fmt.Sprintf(imageToFile, imagePath))
|
||||
|
||||
commandError := command.Run()
|
||||
if commandError != nil {
|
||||
return nil, ErrNoImageInClipboard
|
||||
}
|
||||
|
||||
data, readError := ioutil.ReadFile(imagePath)
|
||||
if readError != nil {
|
||||
return nil, readError
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return nil, ErrNoImageInClipboard
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
// +build linux
|
||||
|
||||
package goclipimg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// ErrImagePasteUnsupported means that xclip(X11) or wl-clipboard(Wayland)
|
||||
// can't be found or isn't installed.
|
||||
var ErrImagePasteUnsupported = errors.New("xclip/wl-clipboard is not available on this system")
|
||||
|
||||
func isCommandAvailable(name string) bool {
|
||||
_, fileError := exec.LookPath(name)
|
||||
return fileError == nil
|
||||
}
|
||||
|
||||
func getImageFromWayland(buffer *bytes.Buffer) error {
|
||||
//The wl-clipboard package has the commands wl-copy and wl-paste
|
||||
if !isCommandAvailable("wl-paste") {
|
||||
return ErrImagePasteUnsupported
|
||||
}
|
||||
|
||||
wlPaste := exec.Command("wl-paste", "-t", "image/png")
|
||||
wlPaste.Stdout = buffer
|
||||
return wlPaste.Run()
|
||||
}
|
||||
|
||||
func getImageFromXclip(buffer *bytes.Buffer) error {
|
||||
if !isCommandAvailable("xclip") {
|
||||
return ErrImagePasteUnsupported
|
||||
}
|
||||
|
||||
xclip := exec.Command("xclip", "-sel", "clipboard", "-t", "image/png", "-o")
|
||||
xclip.Stdout = buffer
|
||||
return xclip.Run()
|
||||
}
|
||||
|
||||
func getImageFromClipboard() ([]byte, error) {
|
||||
var buffer bytes.Buffer
|
||||
//500KB
|
||||
buffer.Grow(500000)
|
||||
sessionType := os.Getenv("XDG_SESSION_TYPE")
|
||||
var clipError error
|
||||
if sessionType == "wayland" {
|
||||
clipError = getImageFromWayland(&buffer)
|
||||
} else {
|
||||
//For everything not wayland, we default to x11
|
||||
clipError = getImageFromXclip(&buffer)
|
||||
}
|
||||
|
||||
if clipError != nil {
|
||||
return nil, ErrNoImageInClipboard
|
||||
}
|
||||
|
||||
image := buffer.Bytes()
|
||||
if len(image) == 0 {
|
||||
return nil, ErrNoImageInClipboard
|
||||
}
|
||||
|
||||
return image, nil
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// +build !linux,!darwin,!windows
|
||||
|
||||
package goclipimg
|
||||
|
||||
import "errors"
|
||||
|
||||
// ErrImagePasteUnsupported means that this system has no implementation for pasting an image.
|
||||
|
||||
var ErrImagePasteUnsupported = errors.New("This system doesn't have a paste implementation.")
|
||||
|
||||
// getImageFromClipboard always returns ErrImagePasteUnsupported, since the
|
||||
|
||||
// compilation target currently doesn't support pasting images.
|
||||
|
||||
func getImageFromClipboard() ([]byte, error) {
|
||||
return nil, ErrImagePasteUnsupported
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// +build windows
|
||||
|
||||
package goclipimg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func getImageFromClipboard() ([]byte, error) {
|
||||
tempFile, tempFileError := ioutil.TempFile("", "clipimg")
|
||||
if tempFileError != nil {
|
||||
return nil, tempFileError
|
||||
}
|
||||
|
||||
imagePath := tempFile.Name()
|
||||
|
||||
tempFile.Close()
|
||||
defer os.Remove(imagePath)
|
||||
|
||||
err := exec.Command("powershell", "-Command", fmt.Sprintf(`Add-Type -Assembly PresentationCore
|
||||
$img = [Windows.Clipboard]::GetImage()
|
||||
if (!($img -eq $null)) {
|
||||
$file = '%s'
|
||||
$stream = [IO.File]::Open($file, 'OpenOrCreate')
|
||||
$encoder = New-Object Windows.Media.Imaging.PngBitmapEncoder
|
||||
$encoder.Frames.Add([Windows.Media.Imaging.BitmapFrame]::Create($img))
|
||||
$encoder.Save($stream)
|
||||
$stream.Dispose()
|
||||
}`, imagePath)).Run()
|
||||
|
||||
if err != nil {
|
||||
return nil, ErrNoImageInClipboard
|
||||
}
|
||||
|
||||
data, readError := ioutil.ReadFile(imagePath)
|
||||
if readError != nil {
|
||||
return nil, readError
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return nil, ErrNoImageInClipboard
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/Bios-Marcel/goclipimg
|
||||
|
||||
go 1.12
|
|
@ -0,0 +1,29 @@
|
|||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2019, Marcel Schramm
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,29 @@
|
|||
# Short but not for long
|
||||
|
||||
[![builds.sr.ht status](https://builds.sr.ht/~biosmarcel/shortnotforlong/arch.yml.svg)](https://builds.sr.ht/~biosmarcel/shortnotforlong/arch.yml?)
|
||||
|
||||
This is a small link shortener written in golang. It's not meant for
|
||||
permanently shortening link. So in case you don't want dead links after a
|
||||
reboot, this is not for you.
|
||||
|
||||
On top of forgetting everything on reboot, it can't hold many links, the
|
||||
upper limit is `math.MaxUint16`.
|
||||
|
||||
## Usage example
|
||||
|
||||
```go
|
||||
func main() {
|
||||
shortener := NewShortener(1234)
|
||||
fmt.Println(shortener.Shorten("https://google.com"))
|
||||
|
||||
blocker := make(chan struct{})
|
||||
go func() {
|
||||
log.Fatalln(shortener.Start())
|
||||
blocker <- struct{}{}
|
||||
}()
|
||||
|
||||
<-blocker
|
||||
}
|
||||
```
|
||||
|
||||
Run it, open your browser and visit the link that the main-function spits out.
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/Bios-Marcel/shortnotforlong
|
||||
|
||||
go 1.12
|
|
@ -0,0 +1,129 @@
|
|||
package linkshortener
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Shortener offers a function to shorten a URL and redirect to the shortened
|
||||
// URL as soon as a request comes in.
|
||||
type Shortener struct {
|
||||
nextFreeIndex uint16
|
||||
shortenedUrls map[uint16]string
|
||||
port int
|
||||
httpServer *http.Server
|
||||
}
|
||||
|
||||
// Shorten takes a url and returns a shortend version that redirects via the
|
||||
// local webserver.
|
||||
func (shortener *Shortener) Shorten(url string) (string, string) {
|
||||
suffix := getSuffix(url)
|
||||
for id, address := range shortener.shortenedUrls {
|
||||
if address == url {
|
||||
return shortener.formatShortenedURL(id), suffix
|
||||
}
|
||||
}
|
||||
|
||||
newID := shortener.popNextIndex()
|
||||
shortener.shortenedUrls[newID] = url
|
||||
|
||||
return shortener.formatShortenedURL(newID), suffix
|
||||
}
|
||||
|
||||
func (shortener *Shortener) formatShortenedURL(id uint16) string {
|
||||
return fmt.Sprintf("http://localhost:%d/%d", shortener.port, id)
|
||||
}
|
||||
|
||||
func getSuffix(url string) string {
|
||||
if strings.Count(url, "/") > 2 {
|
||||
return filepath.Ext(url)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// CalculateShortenedLength returns the length of the shortened URL without
|
||||
// a suffix and the length of the suffix, which is 0 if the suffix isn't available.
|
||||
func (shortener *Shortener) CalculateShortenedLength(url string) (int, int) {
|
||||
id := shortener.peekNextID()
|
||||
return len(shortener.formatShortenedURL(id)), len(getSuffix(url))
|
||||
}
|
||||
|
||||
func (shortener *Shortener) popNextIndex() uint16 {
|
||||
shortener.nextFreeIndex = shortener.peekNextID()
|
||||
return shortener.nextFreeIndex
|
||||
}
|
||||
|
||||
func (shortener *Shortener) peekNextID() uint16 {
|
||||
if shortener.nextFreeIndex >= math.MaxUint16 {
|
||||
return 0
|
||||
}
|
||||
return shortener.nextFreeIndex + 1
|
||||
}
|
||||
|
||||
//RedirectHandler handles all the redirects for the Server.
|
||||
type RedirectHandler struct {
|
||||
Shortener *Shortener
|
||||
}
|
||||
|
||||
func (h RedirectHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
var id = ""
|
||||
var idRegex = regexp.MustCompile(`/(\d*)`)
|
||||
var matches = idRegex.FindStringSubmatch(r.URL.Path)
|
||||
if len(matches) > 1 {
|
||||
id = matches[1]
|
||||
}
|
||||
|
||||
idAsInt, convertError := strconv.ParseUint(id, 10, 16)
|
||||
if convertError != nil {
|
||||
http.NotFound(w, r)
|
||||
} else {
|
||||
url, contains := h.Shortener.shortenedUrls[uint16(idAsInt)]
|
||||
if contains {
|
||||
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the internal http server.
|
||||
func (shortener *Shortener) Close() {
|
||||
shortener.httpServer.Shutdown(context.Background())
|
||||
}
|
||||
|
||||
//NewShortener creates a new server that uses the given port.
|
||||
func NewShortener(port int) *Shortener {
|
||||
shortener := &Shortener{
|
||||
shortenedUrls: make(map[uint16]string),
|
||||
port: port,
|
||||
}
|
||||
|
||||
handler := RedirectHandler{
|
||||
Shortener: shortener,
|
||||
}
|
||||
|
||||
httpServer := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", port),
|
||||
Handler: handler,
|
||||
ReadTimeout: 1 * time.Second,
|
||||
WriteTimeout: 1 * time.Second,
|
||||
MaxHeaderBytes: 1 << 20,
|
||||
}
|
||||
|
||||
shortener.httpServer = httpServer
|
||||
|
||||
return shortener
|
||||
}
|
||||
|
||||
//Start servers the internal http server, blocks and returns an error on
|
||||
//failure.
|
||||
func (shortener *Shortener) Start() error {
|
||||
return shortener.httpServer.ListenAndServe()
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
/cmd/chroma/chroma
|
||||
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||
.glide/
|
||||
|
||||
_models/
|
||||
|
||||
_examples/
|
|
@ -0,0 +1,55 @@
|
|||
run:
|
||||
tests: true
|
||||
skip-dirs:
|
||||
- _examples
|
||||
|
||||
output:
|
||||
print-issued-lines: false
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- maligned
|
||||
- megacheck
|
||||
- lll
|
||||
- gocyclo
|
||||
- dupl
|
||||
- gochecknoglobals
|
||||
- funlen
|
||||
- godox
|
||||
- wsl
|
||||
- gomnd
|
||||
- gocognit
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
gocyclo:
|
||||
min-complexity: 10
|
||||
dupl:
|
||||
threshold: 100
|
||||
goconst:
|
||||
min-len: 8
|
||||
min-occurrences: 3
|
||||
|
||||
issues:
|
||||
max-per-linter: 0
|
||||
max-same: 0
|
||||
exclude-use-default: false
|
||||
exclude:
|
||||
# Captured by errcheck.
|
||||
- '^(G104|G204):'
|
||||
# Very commonly not checked.
|
||||
- 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
|
||||
- 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON|.*\.EntityURN|.*\.GoString|.*\.Pos) should have comment or be unexported'
|
||||
- 'composite literal uses unkeyed fields'
|
||||
- 'declaration of "err" shadows declaration'
|
||||
- 'should not use dot imports'
|
||||
- 'Potential file inclusion via variable'
|
||||
- 'should have comment or be unexported'
|
||||
- 'comment on exported var .* should be of the form'
|
||||
- 'at least one file in a package should have a package comment'
|
||||
- 'string literal contains the Unicode'
|
||||
- 'methods on the same type should have the same receiver name'
|
||||
- '_TokenType_name should be _TokenTypeName'
|
||||
- '`_TokenType_map` should be `_TokenTypeMap`'
|
|
@ -0,0 +1,33 @@
|
|||
project_name: chroma
|
||||
release:
|
||||
github:
|
||||
owner: alecthomas
|
||||
name: chroma
|
||||
brews:
|
||||
-
|
||||
install: bin.install "chroma"
|
||||
builds:
|
||||
- goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- "386"
|
||||
goarm:
|
||||
- "6"
|
||||
main: ./cmd/chroma/main.go
|
||||
ldflags: -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
|
||||
binary: chroma
|
||||
archives:
|
||||
-
|
||||
format: tar.gz
|
||||
name_template: '{{ .Binary }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}{{ if .Arm }}v{{
|
||||
.Arm }}{{ end }}'
|
||||
files:
|
||||
- COPYING
|
||||
- README*
|
||||
snapshot:
|
||||
name_template: SNAPSHOT-{{ .Commit }}
|
||||
checksum:
|
||||
name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt'
|
|
@ -0,0 +1,12 @@
|
|||
sudo: false
|
||||
language: go
|
||||
go:
|
||||
- "1.13.x"
|
||||
script:
|
||||
- go test -v ./...
|
||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s v1.22.2
|
||||
- ./bin/golangci-lint run
|
||||
- git clean -fdx .
|
||||
after_success:
|
||||
curl -sL https://git.io/goreleaser | bash && goreleaser
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (C) 2017 Alec Thomas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,19 @@
|
|||
.PHONY: chromad upload all
|
||||
|
||||
all: README.md tokentype_string.go
|
||||
|
||||
README.md: lexers/*/*.go
|
||||
./table.py
|
||||
|
||||
tokentype_string.go: types.go
|
||||
go generate
|
||||
|
||||
chromad:
|
||||
(cd ./cmd/chromad && go get github.com/GeertJohan/go.rice/rice@master && go install github.com/GeertJohan/go.rice/rice)
|
||||
rm -f chromad
|
||||
(export CGOENABLED=0 GOOS=linux ; cd ./cmd/chromad && go build -o ../../chromad .)
|
||||
rice append -i ./cmd/chromad --exec=./chromad
|
||||
|
||||
upload: chromad
|
||||
scp chromad root@swapoff.org: && \
|
||||
ssh root@swapoff.org 'install -m755 ./chromad /srv/http/swapoff.org/bin && service chromad restart'
|
|
@ -0,0 +1,267 @@
|
|||
# Chroma — A general purpose syntax highlighter in pure Go [![Golang Documentation](https://godoc.org/github.com/alecthomas/chroma?status.svg)](https://godoc.org/github.com/alecthomas/chroma) [![Build Status](https://travis-ci.org/alecthomas/chroma.svg)](https://travis-ci.org/alecthomas/chroma) [![Gitter chat](https://badges.gitter.im/alecthomas.svg)](https://gitter.im/alecthomas/Lobby)
|
||||
|
||||
> **NOTE:** As Chroma has just been released, its API is still in flux. That said, the high-level interface should not change significantly.
|
||||
|
||||
Chroma takes source code and other structured text and converts it into syntax
|
||||
highlighted HTML, ANSI-coloured text, etc.
|
||||
|
||||
Chroma is based heavily on [Pygments](http://pygments.org/), and includes
|
||||
translators for Pygments lexers and styles.
|
||||
|
||||
<a id="markdown-table-of-contents" name="table-of-contents"></a>
|
||||
## Table of Contents
|
||||
|
||||
<!-- TOC -->
|
||||
|
||||
1. [Table of Contents](#table-of-contents)
|
||||
2. [Supported languages](#supported-languages)
|
||||
3. [Try it](#try-it)
|
||||
4. [Using the library](#using-the-library)
|
||||
1. [Quick start](#quick-start)
|
||||
2. [Identifying the language](#identifying-the-language)
|
||||
3. [Formatting the output](#formatting-the-output)
|
||||
4. [The HTML formatter](#the-html-formatter)
|
||||
5. [More detail](#more-detail)
|
||||
1. [Lexers](#lexers)
|
||||
2. [Formatters](#formatters)
|
||||
3. [Styles](#styles)
|
||||
6. [Command-line interface](#command-line-interface)
|
||||
7. [What's missing compared to Pygments?](#whats-missing-compared-to-pygments)
|
||||
|
||||
<!-- /TOC -->
|
||||
|
||||
<a id="markdown-supported-languages" name="supported-languages"></a>
|
||||
## Supported languages
|
||||
|
||||
Prefix | Language
|
||||
:----: | --------
|
||||
A | ABAP, ABNF, ActionScript, ActionScript 3, Ada, Angular2, ANTLR, ApacheConf, APL, AppleScript, Arduino, Awk
|
||||
B | Ballerina, Base Makefile, Bash, Batchfile, BlitzBasic, BNF, Brainfuck
|
||||
C | C, C#, C++, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Crystal, CSS, Cython
|
||||
D | D, Dart, Diff, Django/Jinja, Docker, DTD
|
||||
E | EBNF, Elixir, Elm, EmacsLisp, Erlang
|
||||
F | Factor, Fish, Forth, Fortran, FSharp
|
||||
G | GAS, GDScript, Genshi, Genshi HTML, Genshi Text, GLSL, Gnuplot, Go, Go HTML Template, Go Text Template, GraphQL, Groovy
|
||||
H | Handlebars, Haskell, Haxe, HCL, Hexdump, HTML, HTTP, Hy
|
||||
I | Idris, INI, Io
|
||||
J | J, Java, JavaScript, JSON, Julia, Jungle
|
||||
K | Kotlin
|
||||
L | Lighttpd configuration file, LLVM, Lua
|
||||
M | Mako, markdown, Mason, Mathematica, Matlab, MiniZinc, MLIR, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL
|
||||
N | NASM, Newspeak, Nginx configuration file, Nim, Nix
|
||||
O | Objective-C, OCaml, Octave, OpenSCAD, Org Mode
|
||||
P | PacmanConf, Perl, PHP, Pig, PkgConfig, PL/pgSQL, plaintext, PostgreSQL SQL dialect, PostScript, POVRay, PowerShell, Prolog, Protocol Buffer, Puppet, Python, Python 3
|
||||
Q | QBasic
|
||||
R | R, Racket, Ragel, react, reg, reStructuredText, Rexx, Ruby, Rust
|
||||
S | Sass, Scala, Scheme, Scilab, SCSS, Smalltalk, Smarty, SML, Snobol, Solidity, SPARQL, SQL, SquidConf, Swift, SYSTEMD, systemverilog
|
||||
T | TableGen, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData
|
||||
V | VB.net, verilog, VHDL, VimL, vue
|
||||
W | WDTE
|
||||
X | XML, Xorg
|
||||
Y | YAML
|
||||
|
||||
|
||||
_I will attempt to keep this section up to date, but an authoritative list can be
|
||||
displayed with `chroma --list`._
|
||||
|
||||
<a id="markdown-try-it" name="try-it"></a>
|
||||
## Try it
|
||||
|
||||
Try out various languages and styles on the [Chroma Playground](https://swapoff.org/chroma/playground/).
|
||||
|
||||
<a id="markdown-using-the-library" name="using-the-library"></a>
|
||||
## Using the library
|
||||
|
||||
Chroma, like Pygments, has the concepts of
|
||||
[lexers](https://github.com/alecthomas/chroma/tree/master/lexers),
|
||||
[formatters](https://github.com/alecthomas/chroma/tree/master/formatters) and
|
||||
[styles](https://github.com/alecthomas/chroma/tree/master/styles).
|
||||
|
||||
Lexers convert source text into a stream of tokens, styles specify how token
|
||||
types are mapped to colours, and formatters convert tokens and styles into
|
||||
formatted output.
|
||||
|
||||
A package exists for each of these, containing a global `Registry` variable
|
||||
with all of the registered implementations. There are also helper functions
|
||||
for using the registry in each package, such as looking up lexers by name or
|
||||
matching filenames, etc.
|
||||
|
||||
In all cases, if a lexer, formatter or style can not be determined, `nil` will
|
||||
be returned. In this situation you may want to default to the `Fallback`
|
||||
value in each respective package, which provides sane defaults.
|
||||
|
||||
<a id="markdown-quick-start" name="quick-start"></a>
|
||||
### Quick start
|
||||
|
||||
A convenience function exists that can be used to simply format some source
|
||||
text, without any effort:
|
||||
|
||||
```go
|
||||
err := quick.Highlight(os.Stdout, someSourceCode, "go", "html", "monokai")
|
||||
```
|
||||
|
||||
<a id="markdown-identifying-the-language" name="identifying-the-language"></a>
|
||||
### Identifying the language
|
||||
|
||||
To highlight code, you'll first have to identify what language the code is
|
||||
written in. There are three primary ways to do that:
|
||||
|
||||
1. Detect the language from its filename.
|
||||
|
||||
```go
|
||||
lexer := lexers.Match("foo.go")
|
||||
```
|
||||
|
||||
3. Explicitly specify the language by its Chroma syntax ID (a full list is available from `lexers.Names()`).
|
||||
|
||||
```go
|
||||
lexer := lexers.Get("go")
|
||||
```
|
||||
|
||||
3. Detect the language from its content.
|
||||
|
||||
```go
|
||||
lexer := lexers.Analyse("package main\n\nfunc main()\n{\n}\n")
|
||||
```
|
||||
|
||||
In all cases, `nil` will be returned if the language can not be identified.
|
||||
|
||||
```go
|
||||
if lexer == nil {
|
||||
lexer = lexers.Fallback
|
||||
}
|
||||
```
|
||||
|
||||
At this point, it should be noted that some lexers can be extremely chatty. To
|
||||
mitigate this, you can use the coalescing lexer to coalesce runs of identical
|
||||
token types into a single token:
|
||||
|
||||
```go
|
||||
lexer = chroma.Coalesce(lexer)
|
||||
```
|
||||
|
||||
<a id="markdown-formatting-the-output" name="formatting-the-output"></a>
|
||||
### Formatting the output
|
||||
|
||||
Once a language is identified you will need to pick a formatter and a style (theme).
|
||||
|
||||
```go
|
||||
style := styles.Get("swapoff")
|
||||
if style == nil {
|
||||
style = styles.Fallback
|
||||
}
|
||||
formatter := formatters.Get("html")
|
||||
if formatter == nil {
|
||||
formatter = formatters.Fallback
|
||||
}
|
||||
```
|
||||
|
||||
Then obtain an iterator over the tokens:
|
||||
|
||||
```go
|
||||
contents, err := ioutil.ReadAll(r)
|
||||
iterator, err := lexer.Tokenise(nil, string(contents))
|
||||
```
|
||||
|
||||
And finally, format the tokens from the iterator:
|
||||
|
||||
```go
|
||||
err := formatter.Format(w, style, iterator)
|
||||
```
|
||||
|
||||
<a id="markdown-the-html-formatter" name="the-html-formatter"></a>
|
||||
### The HTML formatter
|
||||
|
||||
By default the `html` registered formatter generates standalone HTML with
|
||||
embedded CSS. More flexibility is available through the `formatters/html` package.
|
||||
|
||||
Firstly, the output generated by the formatter can be customised with the
|
||||
following constructor options:
|
||||
|
||||
- `Standalone()` - generate standalone HTML with embedded CSS.
|
||||
- `WithClasses()` - use classes rather than inlined style attributes.
|
||||
- `ClassPrefix(prefix)` - prefix each generated CSS class.
|
||||
- `TabWidth(width)` - Set the rendered tab width, in characters.
|
||||
- `WithLineNumbers()` - Render line numbers (style with `LineNumbers`).
|
||||
- `LinkableLineNumbers()` - Make the line numbers linkable.
|
||||
- `HighlightLines(ranges)` - Highlight lines in these ranges (style with `LineHighlight`).
|
||||
- `LineNumbersInTable()` - Use a table for formatting line numbers and code, rather than spans.
|
||||
|
||||
If `WithClasses()` is used, the corresponding CSS can be obtained from the formatter with:
|
||||
|
||||
```go
|
||||
formatter := html.New(html.WithClasses())
|
||||
err := formatter.WriteCSS(w, style)
|
||||
```
|
||||
|
||||
<a id="markdown-more-detail" name="more-detail"></a>
|
||||
## More detail
|
||||
|
||||
<a id="markdown-lexers" name="lexers"></a>
|
||||
### Lexers
|
||||
|
||||
See the [Pygments documentation](http://pygments.org/docs/lexerdevelopment/)
|
||||
for details on implementing lexers. Most concepts apply directly to Chroma,
|
||||
but see existing lexer implementations for real examples.
|
||||
|
||||
In many cases lexers can be automatically converted directly from Pygments by
|
||||
using the included Python 3 script `pygments2chroma.py`. I use something like
|
||||
the following:
|
||||
|
||||
```sh
|
||||
python3 ~/Projects/chroma/_tools/pygments2chroma.py \
|
||||
pygments.lexers.jvm.KotlinLexer \
|
||||
> ~/Projects/chroma/lexers/kotlin.go \
|
||||
&& gofmt -s -w ~/Projects/chroma/lexers/*.go
|
||||
```
|
||||
|
||||
See notes in [pygments-lexers.go](https://github.com/alecthomas/chroma/blob/master/pygments-lexers.txt)
|
||||
for a list of lexers, and notes on some of the issues importing them.
|
||||
|
||||
<a id="markdown-formatters" name="formatters"></a>
|
||||
### Formatters
|
||||
|
||||
Chroma supports HTML output, as well as terminal output in 8 colour, 256 colour, and true-colour.
|
||||
|
||||
A `noop` formatter is included that outputs the token text only, and a `tokens`
|
||||
formatter outputs raw tokens. The latter is useful for debugging lexers.
|
||||
|
||||
<a id="markdown-styles" name="styles"></a>
|
||||
### Styles
|
||||
|
||||
Chroma styles use the [same syntax](http://pygments.org/docs/styles/) as Pygments.
|
||||
|
||||
All Pygments styles have been converted to Chroma using the `_tools/style.py` script.
|
||||
|
||||
When you work with one of [Chroma's styles](https://github.com/alecthomas/chroma/tree/master/styles), know that the `chroma.Background` token type provides the default style for tokens. It does so by defining a foreground color and background color.
|
||||
|
||||
For example, this gives each token name not defined in the style a default color of `#f8f8f8` and uses `#000000` for the highlighted code block's background:
|
||||
|
||||
~~~go
|
||||
chroma.Background: "#f8f8f2 bg:#000000",
|
||||
~~~
|
||||
|
||||
Also, token types in a style file are hierarchical. For instance, when `CommentSpecial` is not defined, Chroma uses the token style from `Comment`. So when several comment tokens use the same color, you'll only need to define `Comment` and override the one that has a different color.
|
||||
|
||||
For a quick overview of the available styles and how they look, check out the [Chroma Style Gallery](https://xyproto.github.io/splash/docs/).
|
||||
|
||||
<a id="markdown-command-line-interface" name="command-line-interface"></a>
|
||||
## Command-line interface
|
||||
|
||||
A command-line interface to Chroma is included. It can be installed with:
|
||||
|
||||
```sh
|
||||
go get -u github.com/alecthomas/chroma/cmd/chroma
|
||||
```
|
||||
|
||||
<a id="markdown-whats-missing-compared-to-pygments" name="whats-missing-compared-to-pygments"></a>
|
||||
## What's missing compared to Pygments?
|
||||
|
||||
- Quite a few lexers, for various reasons (pull-requests welcome):
|
||||
- Pygments lexers for complex languages often include custom code to
|
||||
handle certain aspects, such as Perl6's ability to nest code inside
|
||||
regular expressions. These require time and effort to convert.
|
||||
- I mostly only converted languages I had heard of, to reduce the porting cost.
|
||||
- Some more esoteric features of Pygments are omitted for simplicity.
|
||||
- Though the Chroma API supports content detection, very few languages support them.
|
||||
I have plans to implement a statistical analyser at some point, but not enough time.
|
|
@ -0,0 +1,35 @@
|
|||
package chroma
|
||||
|
||||
// Coalesce is a Lexer interceptor that collapses runs of common types into a single token.
|
||||
func Coalesce(lexer Lexer) Lexer { return &coalescer{lexer} }
|
||||
|
||||
type coalescer struct{ Lexer }
|
||||
|
||||
func (d *coalescer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) {
|
||||
var prev Token
|
||||
it, err := d.Lexer.Tokenise(options, text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return func() Token {
|
||||
for token := it(); token != (EOF); token = it() {
|
||||
if len(token.Value) == 0 {
|
||||
continue
|
||||
}
|
||||
if prev == EOF {
|
||||
prev = token
|
||||
} else {
|
||||
if prev.Type == token.Type && len(prev.Value) < 8192 {
|
||||
prev.Value += token.Value
|
||||
} else {
|
||||
out := prev
|
||||
prev = token
|
||||
return out
|
||||
}
|
||||
}
|
||||
}
|
||||
out := prev
|
||||
prev = EOF
|
||||
return out
|
||||
}, nil
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
package chroma
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ANSI2RGB maps ANSI colour names, as supported by Chroma, to hex RGB values.
|
||||
var ANSI2RGB = map[string]string{
|
||||
"#ansiblack": "000000",
|
||||
"#ansidarkred": "7f0000",
|
||||
"#ansidarkgreen": "007f00",
|
||||
"#ansibrown": "7f7fe0",
|
||||
"#ansidarkblue": "00007f",
|
||||
"#ansipurple": "7f007f",
|
||||
"#ansiteal": "007f7f",
|
||||
"#ansilightgray": "e5e5e5",
|
||||
// Normal
|
||||
"#ansidarkgray": "555555",
|
||||
"#ansired": "ff0000",
|
||||
"#ansigreen": "00ff00",
|
||||
"#ansiyellow": "ffff00",
|
||||
"#ansiblue": "0000ff",
|
||||
"#ansifuchsia": "ff00ff",
|
||||
"#ansiturquoise": "00ffff",
|
||||
"#ansiwhite": "ffffff",
|
||||
|
||||
// Aliases without the "ansi" prefix, because...why?
|
||||
"#black": "000000",
|
||||
"#darkred": "7f0000",
|
||||
"#darkgreen": "007f00",
|
||||
"#brown": "7f7fe0",
|
||||
"#darkblue": "00007f",
|
||||
"#purple": "7f007f",
|
||||
"#teal": "007f7f",
|
||||
"#lightgray": "e5e5e5",
|
||||
// Normal
|
||||
"#darkgray": "555555",
|
||||
"#red": "ff0000",
|
||||
"#green": "00ff00",
|
||||
"#yellow": "ffff00",
|
||||
"#blue": "0000ff",
|
||||
"#fuchsia": "ff00ff",
|
||||
"#turquoise": "00ffff",
|
||||
"#white": "ffffff",
|
||||
}
|
||||
|
||||
// Colour represents an RGB colour.
|
||||
type Colour int32
|
||||
|
||||
// NewColour creates a Colour directly from RGB values.
|
||||
func NewColour(r, g, b uint8) Colour {
|
||||
return ParseColour(fmt.Sprintf("%02x%02x%02x", r, g, b))
|
||||
}
|
||||
|
||||
// Distance between this colour and another.
|
||||
//
|
||||
// This uses the approach described here (https://www.compuphase.com/cmetric.htm).
|
||||
// This is not as accurate as LAB, et. al. but is *vastly* simpler and sufficient for our needs.
|
||||
func (c Colour) Distance(e2 Colour) float64 {
|
||||
ar, ag, ab := int64(c.Red()), int64(c.Green()), int64(c.Blue())
|
||||
br, bg, bb := int64(e2.Red()), int64(e2.Green()), int64(e2.Blue())
|
||||
rmean := (ar + br) / 2
|
||||
r := ar - br
|
||||
g := ag - bg
|
||||
b := ab - bb
|
||||
return math.Sqrt(float64((((512 + rmean) * r * r) >> 8) + 4*g*g + (((767 - rmean) * b * b) >> 8)))
|
||||
}
|
||||
|
||||
// Brighten returns a copy of this colour with its brightness adjusted.
|
||||
//
|
||||
// If factor is negative, the colour is darkened.
|
||||
//
|
||||
// Uses approach described here (http://www.pvladov.com/2012/09/make-color-lighter-or-darker.html).
|
||||
func (c Colour) Brighten(factor float64) Colour {
|
||||
r := float64(c.Red())
|
||||
g := float64(c.Green())
|
||||
b := float64(c.Blue())
|
||||
|
||||
if factor < 0 {
|
||||
factor++
|
||||
r *= factor
|
||||
g *= factor
|
||||
b *= factor
|
||||
} else {
|
||||
r = (255-r)*factor + r
|
||||
g = (255-g)*factor + g
|
||||
b = (255-b)*factor + b
|
||||
}
|
||||
return NewColour(uint8(r), uint8(g), uint8(b))
|
||||
}
|
||||
|
||||
// BrightenOrDarken brightens a colour if it is < 0.5 brighteness or darkens if > 0.5 brightness.
|
||||
func (c Colour) BrightenOrDarken(factor float64) Colour {
|
||||
if c.Brightness() < 0.5 {
|
||||
return c.Brighten(factor)
|
||||
}
|
||||
return c.Brighten(-factor)
|
||||
}
|
||||
|
||||
// Brightness of the colour (roughly) in the range 0.0 to 1.0
|
||||
func (c Colour) Brightness() float64 {
|
||||
return (float64(c.Red()) + float64(c.Green()) + float64(c.Blue())) / 255.0 / 3.0
|
||||
}
|
||||
|
||||
// ParseColour in the forms #rgb, #rrggbb, #ansi<colour>, or #<colour>.
|
||||
// Will return an "unset" colour if invalid.
|
||||
func ParseColour(colour string) Colour {
|
||||
colour = normaliseColour(colour)
|
||||
n, err := strconv.ParseUint(colour, 16, 32)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return Colour(n + 1)
|
||||
}
|
||||
|
||||
// MustParseColour is like ParseColour except it panics if the colour is invalid.
|
||||
//
|
||||
// Will panic if colour is in an invalid format.
|
||||
func MustParseColour(colour string) Colour {
|
||||
parsed := ParseColour(colour)
|
||||
if !parsed.IsSet() {
|
||||
panic(fmt.Errorf("invalid colour %q", colour))
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// IsSet returns true if the colour is set.
|
||||
func (c Colour) IsSet() bool { return c != 0 }
|
||||
|
||||
func (c Colour) String() string { return fmt.Sprintf("#%06x", int(c-1)) }
|
||||
func (c Colour) GoString() string { return fmt.Sprintf("Colour(0x%06x)", int(c-1)) }
|
||||
|
||||
// Red component of colour.
|
||||
func (c Colour) Red() uint8 { return uint8(((c - 1) >> 16) & 0xff) }
|
||||
|
||||
// Green component of colour.
|
||||
func (c Colour) Green() uint8 { return uint8(((c - 1) >> 8) & 0xff) }
|
||||
|
||||
// Blue component of colour.
|
||||
func (c Colour) Blue() uint8 { return uint8((c - 1) & 0xff) }
|
||||
|
||||
// Colours is an orderable set of colours.
|
||||
type Colours []Colour
|
||||
|
||||
func (c Colours) Len() int { return len(c) }
|
||||
func (c Colours) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c Colours) Less(i, j int) bool { return c[i] < c[j] }
|
||||
|
||||
// Convert colours to #rrggbb.
|
||||
func normaliseColour(colour string) string {
|
||||
if ansi, ok := ANSI2RGB[colour]; ok {
|
||||
return ansi
|
||||
}
|
||||
if strings.HasPrefix(colour, "#") {
|
||||
colour = colour[1:]
|
||||
if len(colour) == 3 {
|
||||
return colour[0:1] + colour[0:1] + colour[1:2] + colour[1:2] + colour[2:3] + colour[2:3]
|
||||
}
|
||||
}
|
||||
return colour
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package chroma
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
type delegatingLexer struct {
|
||||
root Lexer
|
||||
language Lexer
|
||||
}
|
||||
|
||||
// DelegatingLexer combines two lexers to handle the common case of a language embedded inside another, such as PHP
|
||||
// inside HTML or PHP inside plain text.
|
||||
//
|
||||
// It takes two lexer as arguments: a root lexer and a language lexer. First everything is scanned using the language
|
||||
// lexer, which must return "Other" for unrecognised tokens. Then all "Other" tokens are lexed using the root lexer.
|
||||
// Finally, these two sets of tokens are merged.
|
||||
//
|
||||
// The lexers from the template lexer package use this base lexer.
|
||||
func DelegatingLexer(root Lexer, language Lexer) Lexer {
|
||||
return &delegatingLexer{
|
||||
root: root,
|
||||
language: language,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *delegatingLexer) Config() *Config {
|
||||
return d.language.Config()
|
||||
}
|
||||
|
||||
// An insertion is the character range where language tokens should be inserted.
|
||||
type insertion struct {
|
||||
start, end int
|
||||
tokens []Token
|
||||
}
|
||||
|
||||
func (d *delegatingLexer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) { // nolint: gocognit
|
||||
tokens, err := Tokenise(Coalesce(d.language), options, text)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Compute insertions and gather "Other" tokens.
|
||||
others := &bytes.Buffer{}
|
||||
insertions := []*insertion{}
|
||||
var insert *insertion
|
||||
offset := 0
|
||||
var last Token
|
||||
for _, t := range tokens {
|
||||
if t.Type == Other {
|
||||
if last != EOF && insert != nil && last.Type != Other {
|
||||
insert.end = offset
|
||||
}
|
||||
others.WriteString(t.Value)
|
||||
} else {
|
||||
if last == EOF || last.Type == Other {
|
||||
insert = &insertion{start: offset}
|
||||
insertions = append(insertions, insert)
|
||||
}
|
||||
insert.tokens = append(insert.tokens, t)
|
||||
}
|
||||
last = t
|
||||
offset += len(t.Value)
|
||||
}
|
||||
|
||||
if len(insertions) == 0 {
|
||||
return d.root.Tokenise(options, text)
|
||||
}
|
||||
|
||||
// Lex the other tokens.
|
||||
rootTokens, err := Tokenise(Coalesce(d.root), options, others.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Interleave the two sets of tokens.
|
||||
var out []Token
|
||||
offset = 0 // Offset into text.
|
||||
tokenIndex := 0
|
||||
nextToken := func() Token {
|
||||
if tokenIndex >= len(rootTokens) {
|
||||
return EOF
|
||||
}
|
||||
t := rootTokens[tokenIndex]
|
||||
tokenIndex++
|
||||
return t
|
||||
}
|
||||
insertionIndex := 0
|
||||
nextInsertion := func() *insertion {
|
||||
if insertionIndex >= len(insertions) {
|
||||
return nil
|
||||
}
|
||||
i := insertions[insertionIndex]
|
||||
insertionIndex++
|
||||
return i
|
||||
}
|
||||
t := nextToken()
|
||||
i := nextInsertion()
|
||||
for t != EOF || i != nil {
|
||||
// fmt.Printf("%d->%d:%q %d->%d:%q\n", offset, offset+len(t.Value), t.Value, i.start, i.end, Stringify(i.tokens...))
|
||||
if t == EOF || (i != nil && i.start < offset+len(t.Value)) {
|
||||
var l Token
|
||||
l, t = splitToken(t, i.start-offset)
|
||||
if l != EOF {
|
||||
out = append(out, l)
|
||||
offset += len(l.Value)
|
||||
}
|
||||
out = append(out, i.tokens...)
|
||||
offset += i.end - i.start
|
||||
if t == EOF {
|
||||
t = nextToken()
|
||||
}
|
||||
i = nextInsertion()
|
||||
} else {
|
||||
out = append(out, t)
|
||||
offset += len(t.Value)
|
||||
t = nextToken()
|
||||
}
|
||||
}
|
||||
return Literator(out...), nil
|
||||
}
|
||||
|
||||
func splitToken(t Token, offset int) (l Token, r Token) {
|
||||
if t == EOF {
|
||||
return EOF, EOF
|
||||
}
|
||||
if offset == 0 {
|
||||
return EOF, t
|
||||
}
|
||||
if offset == len(t.Value) {
|
||||
return t, EOF
|
||||
}
|
||||
l = t.Clone()
|
||||
r = t.Clone()
|
||||
l.Value = l.Value[:offset]
|
||||
r.Value = r.Value[offset:]
|
||||
return
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// Package chroma takes source code and other structured text and converts it into syntax highlighted HTML, ANSI-
|
||||
// coloured text, etc.
|
||||
//
|
||||
// Chroma is based heavily on Pygments, and includes translators for Pygments lexers and styles.
|
||||
//
|
||||
// For more information, go here: https://github.com/alecthomas/chroma
|
||||
package chroma
|
|
@ -0,0 +1,43 @@
|
|||
package chroma
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// A Formatter for Chroma lexers.
|
||||
type Formatter interface {
|
||||
// Format returns a formatting function for tokens.
|
||||
//
|
||||
// If the iterator panics, the Formatter should recover.
|
||||
Format(w io.Writer, style *Style, iterator Iterator) error
|
||||
}
|
||||
|
||||
// A FormatterFunc is a Formatter implemented as a function.
|
||||
//
|
||||
// Guards against iterator panics.
|
||||
type FormatterFunc func(w io.Writer, style *Style, iterator Iterator) error
|
||||
|
||||
func (f FormatterFunc) Format(w io.Writer, s *Style, it Iterator) (err error) { // nolint
|
||||
defer func() {
|
||||
if perr := recover(); perr != nil {
|
||||
err = perr.(error)
|
||||
}
|
||||
}()
|
||||
return f(w, s, it)
|
||||
}
|
||||
|
||||
type recoveringFormatter struct {
|
||||
Formatter
|
||||
}
|
||||
|
||||
func (r recoveringFormatter) Format(w io.Writer, s *Style, it Iterator) (err error) {
|
||||
defer func() {
|
||||
if perr := recover(); perr != nil {
|
||||
err = perr.(error)
|
||||
}
|
||||
}()
|
||||
return r.Formatter.Format(w, s, it)
|
||||
}
|
||||
|
||||
// RecoveringFormatter wraps a formatter with panic recovery.
|
||||
func RecoveringFormatter(formatter Formatter) Formatter { return recoveringFormatter{formatter} }
|
|
@ -0,0 +1,57 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
"github.com/alecthomas/chroma/formatters/html"
|
||||
"github.com/alecthomas/chroma/formatters/svg"
|
||||
)
|
||||
|
||||
var (
|
||||
// NoOp formatter.
|
||||
NoOp = Register("noop", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, iterator chroma.Iterator) error {
|
||||
for t := iterator(); t != chroma.EOF; t = iterator() {
|
||||
if _, err := io.WriteString(w, t.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
// Default HTML formatter outputs self-contained HTML.
|
||||
htmlFull = Register("html", html.New(html.Standalone(true), html.WithClasses(true))) // nolint
|
||||
SVG = Register("svg", svg.New(svg.EmbedFont("Liberation Mono", svg.FontLiberationMono, svg.WOFF)))
|
||||
)
|
||||
|
||||
// Fallback formatter.
|
||||
var Fallback = NoOp
|
||||
|
||||
// Registry of Formatters.
|
||||
var Registry = map[string]chroma.Formatter{}
|
||||
|
||||
// Names of registered formatters.
|
||||
func Names() []string {
|
||||
out := []string{}
|
||||
for name := range Registry {
|
||||
out = append(out, name)
|
||||
}
|
||||
sort.Strings(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// Get formatter by name.
|
||||
//
|
||||
// If the given formatter is not found, the Fallback formatter will be returned.
|
||||
func Get(name string) chroma.Formatter {
|
||||
if f, ok := Registry[name]; ok {
|
||||
return f
|
||||
}
|
||||
return Fallback
|
||||
}
|
||||
|
||||
// Register a named formatter.
|
||||
func Register(name string, formatter chroma.Formatter) chroma.Formatter {
|
||||
Registry[name] = formatter
|
||||
return formatter
|
||||
}
|
|
@ -0,0 +1,443 @@
|
|||
package html
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
// Option sets an option of the HTML formatter.
|
||||
type Option func(f *Formatter)
|
||||
|
||||
// Standalone configures the HTML formatter for generating a standalone HTML document.
|
||||
func Standalone(b bool) Option { return func(f *Formatter) { f.standalone = b } }
|
||||
|
||||
// ClassPrefix sets the CSS class prefix.
|
||||
func ClassPrefix(prefix string) Option { return func(f *Formatter) { f.prefix = prefix } }
|
||||
|
||||
// WithClasses emits HTML using CSS classes, rather than inline styles.
|
||||
func WithClasses(b bool) Option { return func(f *Formatter) { f.Classes = b } }
|
||||
|
||||
// WithAllClasses disables an optimisation that omits redundant CSS classes.
|
||||
func WithAllClasses(b bool) Option { return func(f *Formatter) { f.allClasses = b } }
|
||||
|
||||
// TabWidth sets the number of characters for a tab. Defaults to 8.
|
||||
func TabWidth(width int) Option { return func(f *Formatter) { f.tabWidth = width } }
|
||||
|
||||
// PreventSurroundingPre prevents the surrounding pre tags around the generated code.
|
||||
func PreventSurroundingPre(b bool) Option {
|
||||
return func(f *Formatter) {
|
||||
if b {
|
||||
f.preWrapper = nopPreWrapper
|
||||
} else {
|
||||
f.preWrapper = defaultPreWrapper
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WithPreWrapper allows control of the surrounding pre tags.
|
||||
func WithPreWrapper(wrapper PreWrapper) Option {
|
||||
return func(f *Formatter) {
|
||||
f.preWrapper = wrapper
|
||||
}
|
||||
}
|
||||
|
||||
// WithLineNumbers formats output with line numbers.
|
||||
func WithLineNumbers(b bool) Option {
|
||||
return func(f *Formatter) {
|
||||
f.lineNumbers = b
|
||||
}
|
||||
}
|
||||
|
||||
// LineNumbersInTable will, when combined with WithLineNumbers, separate the line numbers
|
||||
// and code in table td's, which make them copy-and-paste friendly.
|
||||
func LineNumbersInTable(b bool) Option {
|
||||
return func(f *Formatter) {
|
||||
f.lineNumbersInTable = b
|
||||
}
|
||||
}
|
||||
|
||||
// LinkableLineNumbers decorates the line numbers HTML elements with an "id"
|
||||
// attribute so they can be linked.
|
||||
func LinkableLineNumbers(b bool, prefix string) Option {
|
||||
return func(f *Formatter) {
|
||||
f.linkableLineNumbers = b
|
||||
f.lineNumbersIDPrefix = prefix
|
||||
}
|
||||
}
|
||||
|
||||
// HighlightLines higlights the given line ranges with the Highlight style.
|
||||
//
|
||||
// A range is the beginning and ending of a range as 1-based line numbers, inclusive.
|
||||
func HighlightLines(ranges [][2]int) Option {
|
||||
return func(f *Formatter) {
|
||||
f.highlightRanges = ranges
|
||||
sort.Sort(f.highlightRanges)
|
||||
}
|
||||
}
|
||||
|
||||
// BaseLineNumber sets the initial number to start line numbering at. Defaults to 1.
|
||||
func BaseLineNumber(n int) Option {
|
||||
return func(f *Formatter) {
|
||||
f.baseLineNumber = n
|
||||
}
|
||||
}
|
||||
|
||||
// New HTML formatter.
|
||||
func New(options ...Option) *Formatter {
|
||||
f := &Formatter{
|
||||
baseLineNumber: 1,
|
||||
preWrapper: defaultPreWrapper,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(f)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// PreWrapper defines the operations supported in WithPreWrapper.
|
||||
type PreWrapper interface {
|
||||
// Start is called to write a start <pre> element.
|
||||
// The code flag tells whether this block surrounds
|
||||
// highlighted code. This will be false when surrounding
|
||||
// line numbers.
|
||||
Start(code bool, styleAttr string) string
|
||||
|
||||
// End is called to write the end </pre> element.
|
||||
End(code bool) string
|
||||
}
|
||||
|
||||
type preWrapper struct {
|
||||
start func(code bool, styleAttr string) string
|
||||
end func(code bool) string
|
||||
}
|
||||
|
||||
func (p preWrapper) Start(code bool, styleAttr string) string {
|
||||
return p.start(code, styleAttr)
|
||||
}
|
||||
|
||||
func (p preWrapper) End(code bool) string {
|
||||
return p.end(code)
|
||||
}
|
||||
|
||||
var (
|
||||
nopPreWrapper = preWrapper{
|
||||
start: func(code bool, styleAttr string) string { return "" },
|
||||
end: func(code bool) string { return "" },
|
||||
}
|
||||
defaultPreWrapper = preWrapper{
|
||||
start: func(code bool, styleAttr string) string {
|
||||
return fmt.Sprintf("<pre%s>", styleAttr)
|
||||
},
|
||||
end: func(code bool) string {
|
||||
return "</pre>"
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Formatter that generates HTML.
|
||||
type Formatter struct {
|
||||
standalone bool
|
||||
prefix string
|
||||
Classes bool // Exported field to detect when classes are being used
|
||||
allClasses bool
|
||||
preWrapper PreWrapper
|
||||
tabWidth int
|
||||
lineNumbers bool
|
||||
lineNumbersInTable bool
|
||||
linkableLineNumbers bool
|
||||
lineNumbersIDPrefix string
|
||||
highlightRanges highlightRanges
|
||||
baseLineNumber int
|
||||
}
|
||||
|
||||
type highlightRanges [][2]int
|
||||
|
||||
func (h highlightRanges) Len() int { return len(h) }
|
||||
func (h highlightRanges) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||
func (h highlightRanges) Less(i, j int) bool { return h[i][0] < h[j][0] }
|
||||
|
||||
func (f *Formatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) (err error) {
|
||||
return f.writeHTML(w, style, iterator.Tokens())
|
||||
}
|
||||
|
||||
// We deliberately don't use html/template here because it is two orders of magnitude slower (benchmarked).
|
||||
//
|
||||
// OTOH we need to be super careful about correct escaping...
|
||||
func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.Token) (err error) { // nolint: gocyclo
|
||||
css := f.styleToCSS(style)
|
||||
if !f.Classes {
|
||||
for t, style := range css {
|
||||
css[t] = compressStyle(style)
|
||||
}
|
||||
}
|
||||
if f.standalone {
|
||||
fmt.Fprint(w, "<html>\n")
|
||||
if f.Classes {
|
||||
fmt.Fprint(w, "<style type=\"text/css\">\n")
|
||||
err = f.WriteCSS(w, style)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(w, "body { %s; }\n", css[chroma.Background])
|
||||
fmt.Fprint(w, "</style>")
|
||||
}
|
||||
fmt.Fprintf(w, "<body%s>\n", f.styleAttr(css, chroma.Background))
|
||||
}
|
||||
|
||||
wrapInTable := f.lineNumbers && f.lineNumbersInTable
|
||||
|
||||
lines := chroma.SplitTokensIntoLines(tokens)
|
||||
lineDigits := len(fmt.Sprintf("%d", f.baseLineNumber+len(lines)-1))
|
||||
highlightIndex := 0
|
||||
|
||||
if wrapInTable {
|
||||
// List line numbers in its own <td>
|
||||
fmt.Fprintf(w, "<div%s>\n", f.styleAttr(css, chroma.Background))
|
||||
fmt.Fprintf(w, "<table%s><tr>", f.styleAttr(css, chroma.LineTable))
|
||||
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD))
|
||||
fmt.Fprintf(w, f.preWrapper.Start(false, f.styleAttr(css, chroma.Background)))
|
||||
for index := range lines {
|
||||
line := f.baseLineNumber + index
|
||||
highlight, next := f.shouldHighlight(highlightIndex, line)
|
||||
if next {
|
||||
highlightIndex++
|
||||
}
|
||||
if highlight {
|
||||
fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight))
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "<span%s%s>%*d\n</span>", f.styleAttr(css, chroma.LineNumbersTable), f.lineIDAttribute(line), lineDigits, line)
|
||||
|
||||
if highlight {
|
||||
fmt.Fprintf(w, "</span>")
|
||||
}
|
||||
}
|
||||
fmt.Fprint(w, f.preWrapper.End(false))
|
||||
fmt.Fprint(w, "</td>\n")
|
||||
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD, "width:100%"))
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, f.preWrapper.Start(true, f.styleAttr(css, chroma.Background)))
|
||||
|
||||
highlightIndex = 0
|
||||
for index, tokens := range lines {
|
||||
// 1-based line number.
|
||||
line := f.baseLineNumber + index
|
||||
highlight, next := f.shouldHighlight(highlightIndex, line)
|
||||
if next {
|
||||
highlightIndex++
|
||||
}
|
||||
if highlight {
|
||||
fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight))
|
||||
}
|
||||
|
||||
if f.lineNumbers && !wrapInTable {
|
||||
fmt.Fprintf(w, "<span%s%s>%*d</span>", f.styleAttr(css, chroma.LineNumbers), f.lineIDAttribute(line), lineDigits, line)
|
||||
}
|
||||
|
||||
for _, token := range tokens {
|
||||
html := html.EscapeString(token.String())
|
||||
attr := f.styleAttr(css, token.Type)
|
||||
if attr != "" {
|
||||
html = fmt.Sprintf("<span%s>%s</span>", attr, html)
|
||||
}
|
||||
fmt.Fprint(w, html)
|
||||
}
|
||||
if highlight {
|
||||
fmt.Fprintf(w, "</span>")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, f.preWrapper.End(true))
|
||||
|
||||
if wrapInTable {
|
||||
fmt.Fprint(w, "</td></tr></table>\n")
|
||||
fmt.Fprint(w, "</div>\n")
|
||||
}
|
||||
|
||||
if f.standalone {
|
||||
fmt.Fprint(w, "\n</body>\n")
|
||||
fmt.Fprint(w, "</html>\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Formatter) lineIDAttribute(line int) string {
|
||||
if !f.linkableLineNumbers {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(" id=\"%s%d\"", f.lineNumbersIDPrefix, line)
|
||||
}
|
||||
|
||||
func (f *Formatter) shouldHighlight(highlightIndex, line int) (bool, bool) {
|
||||
next := false
|
||||
for highlightIndex < len(f.highlightRanges) && line > f.highlightRanges[highlightIndex][1] {
|
||||
highlightIndex++
|
||||
next = true
|
||||
}
|
||||
if highlightIndex < len(f.highlightRanges) {
|
||||
hrange := f.highlightRanges[highlightIndex]
|
||||
if line >= hrange[0] && line <= hrange[1] {
|
||||
return true, next
|
||||
}
|
||||
}
|
||||
return false, next
|
||||
}
|
||||
|
||||
func (f *Formatter) class(t chroma.TokenType) string {
|
||||
for t != 0 {
|
||||
if cls, ok := chroma.StandardTypes[t]; ok {
|
||||
if cls != "" {
|
||||
return f.prefix + cls
|
||||
}
|
||||
return ""
|
||||
}
|
||||
t = t.Parent()
|
||||
}
|
||||
if cls := chroma.StandardTypes[t]; cls != "" {
|
||||
return f.prefix + cls
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (f *Formatter) styleAttr(styles map[chroma.TokenType]string, tt chroma.TokenType, extraCSS ...string) string {
|
||||
if f.Classes {
|
||||
cls := f.class(tt)
|
||||
if cls == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(` class="%s"`, cls)
|
||||
}
|
||||
if _, ok := styles[tt]; !ok {
|
||||
tt = tt.SubCategory()
|
||||
if _, ok := styles[tt]; !ok {
|
||||
tt = tt.Category()
|
||||
if _, ok := styles[tt]; !ok {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
css := []string{styles[tt]}
|
||||
css = append(css, extraCSS...)
|
||||
return fmt.Sprintf(` style="%s"`, strings.Join(css, ";"))
|
||||
}
|
||||
|
||||
func (f *Formatter) tabWidthStyle() string {
|
||||
if f.tabWidth != 0 && f.tabWidth != 8 {
|
||||
return fmt.Sprintf("; -moz-tab-size: %[1]d; -o-tab-size: %[1]d; tab-size: %[1]d", f.tabWidth)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// WriteCSS writes CSS style definitions (without any surrounding HTML).
|
||||
func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error {
|
||||
css := f.styleToCSS(style)
|
||||
// Special-case background as it is mapped to the outer ".chroma" class.
|
||||
if _, err := fmt.Fprintf(w, "/* %s */ .%schroma { %s }\n", chroma.Background, f.prefix, css[chroma.Background]); err != nil {
|
||||
return err
|
||||
}
|
||||
// Special-case code column of table to expand width.
|
||||
if f.lineNumbers && f.lineNumbersInTable {
|
||||
if _, err := fmt.Fprintf(w, "/* %s */ .%schroma .%s:last-child { width: 100%%; }",
|
||||
chroma.LineTableTD, f.prefix, f.class(chroma.LineTableTD)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Special-case line number highlighting when targeted.
|
||||
if f.lineNumbers || f.lineNumbersInTable {
|
||||
targetedLineCSS := StyleEntryToCSS(style.Get(chroma.LineHighlight))
|
||||
for _, tt := range []chroma.TokenType{chroma.LineNumbers, chroma.LineNumbersTable} {
|
||||
fmt.Fprintf(w, "/* %s targeted by URL anchor */ .%schroma .%s:target { %s }\n", tt, f.prefix, f.class(tt), targetedLineCSS)
|
||||
}
|
||||
}
|
||||
tts := []int{}
|
||||
for tt := range css {
|
||||
tts = append(tts, int(tt))
|
||||
}
|
||||
sort.Ints(tts)
|
||||
for _, ti := range tts {
|
||||
tt := chroma.TokenType(ti)
|
||||
if tt == chroma.Background {
|
||||
continue
|
||||
}
|
||||
class := f.class(tt)
|
||||
if class == "" {
|
||||
continue
|
||||
}
|
||||
styles := css[tt]
|
||||
if _, err := fmt.Fprintf(w, "/* %s */ .%schroma .%s { %s }\n", tt, f.prefix, class, styles); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string {
|
||||
classes := map[chroma.TokenType]string{}
|
||||
bg := style.Get(chroma.Background)
|
||||
// Convert the style.
|
||||
for t := range chroma.StandardTypes {
|
||||
entry := style.Get(t)
|
||||
if t != chroma.Background {
|
||||
entry = entry.Sub(bg)
|
||||
}
|
||||
if !f.allClasses && entry.IsZero() {
|
||||
continue
|
||||
}
|
||||
classes[t] = StyleEntryToCSS(entry)
|
||||
}
|
||||
classes[chroma.Background] += f.tabWidthStyle()
|
||||
lineNumbersStyle := "margin-right: 0.4em; padding: 0 0.4em 0 0.4em;"
|
||||
// All rules begin with default rules followed by user provided rules
|
||||
classes[chroma.LineNumbers] = lineNumbersStyle + classes[chroma.LineNumbers]
|
||||
classes[chroma.LineNumbersTable] = lineNumbersStyle + classes[chroma.LineNumbersTable]
|
||||
classes[chroma.LineHighlight] = "display: block; width: 100%;" + classes[chroma.LineHighlight]
|
||||
classes[chroma.LineTable] = "border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block;" + classes[chroma.LineTable]
|
||||
classes[chroma.LineTableTD] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTableTD]
|
||||
return classes
|
||||
}
|
||||
|
||||
// StyleEntryToCSS converts a chroma.StyleEntry to CSS attributes.
|
||||
func StyleEntryToCSS(e chroma.StyleEntry) string {
|
||||
styles := []string{}
|
||||
if e.Colour.IsSet() {
|
||||
styles = append(styles, "color: "+e.Colour.String())
|
||||
}
|
||||
if e.Background.IsSet() {
|
||||
styles = append(styles, "background-color: "+e.Background.String())
|
||||
}
|
||||
if e.Bold == chroma.Yes {
|
||||
styles = append(styles, "font-weight: bold")
|
||||
}
|
||||
if e.Italic == chroma.Yes {
|
||||
styles = append(styles, "font-style: italic")
|
||||
}
|
||||
if e.Underline == chroma.Yes {
|
||||
styles = append(styles, "text-decoration: underline")
|
||||
}
|
||||
return strings.Join(styles, "; ")
|
||||
}
|
||||
|
||||
// Compress CSS attributes - remove spaces, transform 6-digit colours to 3.
|
||||
func compressStyle(s string) string {
|
||||
parts := strings.Split(s, ";")
|
||||
out := []string{}
|
||||
for _, p := range parts {
|
||||
p = strings.Join(strings.Fields(p), " ")
|
||||
p = strings.Replace(p, ": ", ":", 1)
|
||||
if strings.Contains(p, "#") {
|
||||
c := p[len(p)-6:]
|
||||
if c[0] == c[1] && c[2] == c[3] && c[4] == c[5] {
|
||||
p = p[:len(p)-6] + c[0:1] + c[2:3] + c[4:5]
|
||||
}
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return strings.Join(out, ";")
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
// JSON formatter outputs the raw token structures as JSON.
|
||||
var JSON = Register("json", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, it chroma.Iterator) error {
|
||||
fmt.Fprintln(w, "[")
|
||||
i := 0
|
||||
for t := it(); t != chroma.EOF; t = it() {
|
||||
if i > 0 {
|
||||
fmt.Fprintln(w, ",")
|
||||
}
|
||||
i++
|
||||
bytes, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := fmt.Fprint(w, " "+string(bytes)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
fmt.Fprintln(w, "]")
|
||||
return nil
|
||||
}))
|
51
vendor/github.com/alecthomas/chroma/formatters/svg/font_liberation_mono.go
generated
vendored
Normal file
51
vendor/github.com/alecthomas/chroma/formatters/svg/font_liberation_mono.go
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,222 @@
|
|||
// Package svg contains an SVG formatter.
|
||||
package svg
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
// Option sets an option of the SVG formatter.
|
||||
type Option func(f *Formatter)
|
||||
|
||||
// FontFamily sets the font-family.
|
||||
func FontFamily(fontFamily string) Option { return func(f *Formatter) { f.fontFamily = fontFamily } }
|
||||
|
||||
// EmbedFontFile embeds given font file
|
||||
func EmbedFontFile(fontFamily string, fileName string) (option Option, err error) {
|
||||
var format FontFormat
|
||||
switch path.Ext(fileName) {
|
||||
case ".woff":
|
||||
format = WOFF
|
||||
case ".woff2":
|
||||
format = WOFF2
|
||||
case ".ttf":
|
||||
format = TRUETYPE
|
||||
default:
|
||||
return nil, errors.New("unexpected font file suffix")
|
||||
}
|
||||
|
||||
var content []byte
|
||||
if content, err = ioutil.ReadFile(fileName); err == nil {
|
||||
option = EmbedFont(fontFamily, base64.StdEncoding.EncodeToString(content), format)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// EmbedFont embeds given base64 encoded font
|
||||
func EmbedFont(fontFamily string, font string, format FontFormat) Option {
|
||||
return func(f *Formatter) { f.fontFamily = fontFamily; f.embeddedFont = font; f.fontFormat = format }
|
||||
}
|
||||
|
||||
// New SVG formatter.
|
||||
func New(options ...Option) *Formatter {
|
||||
f := &Formatter{fontFamily: "Consolas, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace"}
|
||||
for _, option := range options {
|
||||
option(f)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// Formatter that generates SVG.
|
||||
type Formatter struct {
|
||||
fontFamily string
|
||||
embeddedFont string
|
||||
fontFormat FontFormat
|
||||
}
|
||||
|
||||
func (f *Formatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Iterator) (err error) {
|
||||
f.writeSVG(w, style, iterator.Tokens())
|
||||
return err
|
||||
}
|
||||
|
||||
var svgEscaper = strings.NewReplacer(
|
||||
`&`, "&",
|
||||
`<`, "<",
|
||||
`>`, ">",
|
||||
`"`, """,
|
||||
` `, " ",
|
||||
` `, "    ",
|
||||
)
|
||||
|
||||
// EscapeString escapes special characters.
|
||||
func escapeString(s string) string {
|
||||
return svgEscaper.Replace(s)
|
||||
}
|
||||
|
||||
func (f *Formatter) writeSVG(w io.Writer, style *chroma.Style, tokens []chroma.Token) { // nolint: gocyclo
|
||||
svgStyles := f.styleToSVG(style)
|
||||
lines := chroma.SplitTokensIntoLines(tokens)
|
||||
|
||||
fmt.Fprint(w, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
|
||||
fmt.Fprint(w, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\" \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">\n")
|
||||
fmt.Fprintf(w, "<svg width=\"%dpx\" height=\"%dpx\" xmlns=\"http://www.w3.org/2000/svg\">\n", 8*maxLineWidth(lines), 10+int(16.8*float64(len(lines)+1)))
|
||||
|
||||
if f.embeddedFont != "" {
|
||||
f.writeFontStyle(w)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "<rect width=\"100%%\" height=\"100%%\" fill=\"%s\"/>\n", style.Get(chroma.Background).Background.String())
|
||||
fmt.Fprintf(w, "<g font-family=\"%s\" font-size=\"14px\" fill=\"%s\">\n", f.fontFamily, style.Get(chroma.Text).Colour.String())
|
||||
|
||||
f.writeTokenBackgrounds(w, lines, style)
|
||||
|
||||
for index, tokens := range lines {
|
||||
fmt.Fprintf(w, "<text x=\"0\" y=\"%fem\" xml:space=\"preserve\">", 1.2*float64(index+1))
|
||||
|
||||
for _, token := range tokens {
|
||||
text := escapeString(token.String())
|
||||
attr := f.styleAttr(svgStyles, token.Type)
|
||||
if attr != "" {
|
||||
text = fmt.Sprintf("<tspan %s>%s</tspan>", attr, text)
|
||||
}
|
||||
fmt.Fprint(w, text)
|
||||
}
|
||||
fmt.Fprint(w, "</text>")
|
||||
}
|
||||
|
||||
fmt.Fprint(w, "\n</g>\n")
|
||||
fmt.Fprint(w, "</svg>\n")
|
||||
}
|
||||
|
||||
func maxLineWidth(lines [][]chroma.Token) int {
|
||||
maxWidth := 0
|
||||
for _, tokens := range lines {
|
||||
length := 0
|
||||
for _, token := range tokens {
|
||||
length += len(strings.Replace(token.String(), ` `, " ", -1))
|
||||
}
|
||||
if length > maxWidth {
|
||||
maxWidth = length
|
||||
}
|
||||
}
|
||||
return maxWidth
|
||||
}
|
||||
|
||||
// There is no background attribute for text in SVG so simply calculate the position and text
|
||||
// of tokens with a background color that differs from the default and add a rectangle for each before
|
||||
// adding the token.
|
||||
func (f *Formatter) writeTokenBackgrounds(w io.Writer, lines [][]chroma.Token, style *chroma.Style) {
|
||||
for index, tokens := range lines {
|
||||
lineLength := 0
|
||||
for _, token := range tokens {
|
||||
length := len(strings.Replace(token.String(), ` `, " ", -1))
|
||||
tokenBackground := style.Get(token.Type).Background
|
||||
if tokenBackground.IsSet() && tokenBackground != style.Get(chroma.Background).Background {
|
||||
fmt.Fprintf(w, "<rect id=\"%s\" x=\"%dch\" y=\"%fem\" width=\"%dch\" height=\"1.2em\" fill=\"%s\" />\n", escapeString(token.String()), lineLength, 1.2*float64(index)+0.25, length, style.Get(token.Type).Background.String())
|
||||
}
|
||||
lineLength += length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type FontFormat int
|
||||
|
||||
// https://transfonter.org/formats
|
||||
const (
|
||||
WOFF FontFormat = iota
|
||||
WOFF2
|
||||
TRUETYPE
|
||||
)
|
||||
|
||||
var fontFormats = [...]string{
|
||||
"woff",
|
||||
"woff2",
|
||||
"truetype",
|
||||
}
|
||||
|
||||
func (f *Formatter) writeFontStyle(w io.Writer) {
|
||||
fmt.Fprintf(w, `<style>
|
||||
@font-face {
|
||||
font-family: '%s';
|
||||
src: url(data:application/x-font-%s;charset=utf-8;base64,%s) format('%s');'
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
</style>`, f.fontFamily, fontFormats[f.fontFormat], f.embeddedFont, fontFormats[f.fontFormat])
|
||||
}
|
||||
|
||||
func (f *Formatter) styleAttr(styles map[chroma.TokenType]string, tt chroma.TokenType) string {
|
||||
if _, ok := styles[tt]; !ok {
|
||||
tt = tt.SubCategory()
|
||||
if _, ok := styles[tt]; !ok {
|
||||
tt = tt.Category()
|
||||
if _, ok := styles[tt]; !ok {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return styles[tt]
|
||||
}
|
||||
|
||||
func (f *Formatter) styleToSVG(style *chroma.Style) map[chroma.TokenType]string {
|
||||
converted := map[chroma.TokenType]string{}
|
||||
bg := style.Get(chroma.Background)
|
||||
// Convert the style.
|
||||
for t := range chroma.StandardTypes {
|
||||
entry := style.Get(t)
|
||||
if t != chroma.Background {
|
||||
entry = entry.Sub(bg)
|
||||
}
|
||||
if entry.IsZero() {
|
||||
continue
|
||||
}
|
||||
converted[t] = StyleEntryToSVG(entry)
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
// StyleEntryToSVG converts a chroma.StyleEntry to SVG attributes.
|
||||
func StyleEntryToSVG(e chroma.StyleEntry) string {
|
||||
var styles []string
|
||||
|
||||
if e.Colour.IsSet() {
|
||||
styles = append(styles, "fill=\""+e.Colour.String()+"\"")
|
||||
}
|
||||
if e.Bold == chroma.Yes {
|
||||
styles = append(styles, "font-weight=\"bold\"")
|
||||
}
|
||||
if e.Italic == chroma.Yes {
|
||||
styles = append(styles, "font-style=\"italic\"")
|
||||
}
|
||||
if e.Underline == chroma.Yes {
|
||||
styles = append(styles, "text-decoration=\"underline\"")
|
||||
}
|
||||
return strings.Join(styles, " ")
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
// Tokens formatter outputs the raw token structures.
|
||||
var Tokens = Register("tokens", chroma.FormatterFunc(func(w io.Writer, s *chroma.Style, it chroma.Iterator) error {
|
||||
for t := it(); t != chroma.EOF; t = it() {
|
||||
if _, err := fmt.Fprintln(w, t.GoString()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}))
|
|
@ -0,0 +1,260 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
type ttyTable struct {
|
||||
foreground map[chroma.Colour]string
|
||||
background map[chroma.Colour]string
|
||||
}
|
||||
|
||||
var c = chroma.MustParseColour
|
||||
|
||||
var ttyTables = map[int]*ttyTable{
|
||||
8: {
|
||||
foreground: map[chroma.Colour]string{
|
||||
c("#000000"): "\033[30m", c("#7f0000"): "\033[31m", c("#007f00"): "\033[32m", c("#7f7fe0"): "\033[33m",
|
||||
c("#00007f"): "\033[34m", c("#7f007f"): "\033[35m", c("#007f7f"): "\033[36m", c("#e5e5e5"): "\033[37m",
|
||||
c("#555555"): "\033[90m", c("#ff0000"): "\033[91m", c("#00ff00"): "\033[92m", c("#ffff00"): "\033[93m",
|
||||
c("#0000ff"): "\033[94m", c("#ff00ff"): "\033[95m", c("#00ffff"): "\033[96m", c("#ffffff"): "\033[97m",
|
||||
},
|
||||
background: map[chroma.Colour]string{
|
||||
c("#000000"): "\033[40m", c("#7f0000"): "\033[41m", c("#007f00"): "\033[42m", c("#7f7fe0"): "\033[43m",
|
||||
c("#00007f"): "\033[44m", c("#7f007f"): "\033[45m", c("#007f7f"): "\033[46m", c("#e5e5e5"): "\033[47m",
|
||||
c("#555555"): "\033[100m", c("#ff0000"): "\033[101m", c("#00ff00"): "\033[102m", c("#ffff00"): "\033[103m",
|
||||
c("#0000ff"): "\033[104m", c("#ff00ff"): "\033[105m", c("#00ffff"): "\033[106m", c("#ffffff"): "\033[107m",
|
||||
},
|
||||
},
|
||||
256: {
|
||||
foreground: map[chroma.Colour]string{
|
||||
c("#000000"): "\033[38;5;0m", c("#800000"): "\033[38;5;1m", c("#008000"): "\033[38;5;2m", c("#808000"): "\033[38;5;3m",
|
||||
c("#000080"): "\033[38;5;4m", c("#800080"): "\033[38;5;5m", c("#008080"): "\033[38;5;6m", c("#c0c0c0"): "\033[38;5;7m",
|
||||
c("#808080"): "\033[38;5;8m", c("#ff0000"): "\033[38;5;9m", c("#00ff00"): "\033[38;5;10m", c("#ffff00"): "\033[38;5;11m",
|
||||
c("#0000ff"): "\033[38;5;12m", c("#ff00ff"): "\033[38;5;13m", c("#00ffff"): "\033[38;5;14m", c("#ffffff"): "\033[38;5;15m",
|
||||
c("#000000"): "\033[38;5;16m", c("#00005f"): "\033[38;5;17m", c("#000087"): "\033[38;5;18m", c("#0000af"): "\033[38;5;19m",
|
||||
c("#0000d7"): "\033[38;5;20m", c("#0000ff"): "\033[38;5;21m", c("#005f00"): "\033[38;5;22m", c("#005f5f"): "\033[38;5;23m",
|
||||
c("#005f87"): "\033[38;5;24m", c("#005faf"): "\033[38;5;25m", c("#005fd7"): "\033[38;5;26m", c("#005fff"): "\033[38;5;27m",
|
||||
c("#008700"): "\033[38;5;28m", c("#00875f"): "\033[38;5;29m", c("#008787"): "\033[38;5;30m", c("#0087af"): "\033[38;5;31m",
|
||||
c("#0087d7"): "\033[38;5;32m", c("#0087ff"): "\033[38;5;33m", c("#00af00"): "\033[38;5;34m", c("#00af5f"): "\033[38;5;35m",
|
||||
c("#00af87"): "\033[38;5;36m", c("#00afaf"): "\033[38;5;37m", c("#00afd7"): "\033[38;5;38m", c("#00afff"): "\033[38;5;39m",
|
||||
c("#00d700"): "\033[38;5;40m", c("#00d75f"): "\033[38;5;41m", c("#00d787"): "\033[38;5;42m", c("#00d7af"): "\033[38;5;43m",
|
||||
c("#00d7d7"): "\033[38;5;44m", c("#00d7ff"): "\033[38;5;45m", c("#00ff00"): "\033[38;5;46m", c("#00ff5f"): "\033[38;5;47m",
|
||||
c("#00ff87"): "\033[38;5;48m", c("#00ffaf"): "\033[38;5;49m", c("#00ffd7"): "\033[38;5;50m", c("#00ffff"): "\033[38;5;51m",
|
||||
c("#5f0000"): "\033[38;5;52m", c("#5f005f"): "\033[38;5;53m", c("#5f0087"): "\033[38;5;54m", c("#5f00af"): "\033[38;5;55m",
|
||||
c("#5f00d7"): "\033[38;5;56m", c("#5f00ff"): "\033[38;5;57m", c("#5f5f00"): "\033[38;5;58m", c("#5f5f5f"): "\033[38;5;59m",
|
||||
c("#5f5f87"): "\033[38;5;60m", c("#5f5faf"): "\033[38;5;61m", c("#5f5fd7"): "\033[38;5;62m", c("#5f5fff"): "\033[38;5;63m",
|
||||
c("#5f8700"): "\033[38;5;64m", c("#5f875f"): "\033[38;5;65m", c("#5f8787"): "\033[38;5;66m", c("#5f87af"): "\033[38;5;67m",
|
||||
c("#5f87d7"): "\033[38;5;68m", c("#5f87ff"): "\033[38;5;69m", c("#5faf00"): "\033[38;5;70m", c("#5faf5f"): "\033[38;5;71m",
|
||||
c("#5faf87"): "\033[38;5;72m", c("#5fafaf"): "\033[38;5;73m", c("#5fafd7"): "\033[38;5;74m", c("#5fafff"): "\033[38;5;75m",
|
||||
c("#5fd700"): "\033[38;5;76m", c("#5fd75f"): "\033[38;5;77m", c("#5fd787"): "\033[38;5;78m", c("#5fd7af"): "\033[38;5;79m",
|
||||
c("#5fd7d7"): "\033[38;5;80m", c("#5fd7ff"): "\033[38;5;81m", c("#5fff00"): "\033[38;5;82m", c("#5fff5f"): "\033[38;5;83m",
|
||||
c("#5fff87"): "\033[38;5;84m", c("#5fffaf"): "\033[38;5;85m", c("#5fffd7"): "\033[38;5;86m", c("#5fffff"): "\033[38;5;87m",
|
||||
c("#870000"): "\033[38;5;88m", c("#87005f"): "\033[38;5;89m", c("#870087"): "\033[38;5;90m", c("#8700af"): "\033[38;5;91m",
|
||||
c("#8700d7"): "\033[38;5;92m", c("#8700ff"): "\033[38;5;93m", c("#875f00"): "\033[38;5;94m", c("#875f5f"): "\033[38;5;95m",
|
||||
c("#875f87"): "\033[38;5;96m", c("#875faf"): "\033[38;5;97m", c("#875fd7"): "\033[38;5;98m", c("#875fff"): "\033[38;5;99m",
|
||||
c("#878700"): "\033[38;5;100m", c("#87875f"): "\033[38;5;101m", c("#878787"): "\033[38;5;102m", c("#8787af"): "\033[38;5;103m",
|
||||
c("#8787d7"): "\033[38;5;104m", c("#8787ff"): "\033[38;5;105m", c("#87af00"): "\033[38;5;106m", c("#87af5f"): "\033[38;5;107m",
|
||||
c("#87af87"): "\033[38;5;108m", c("#87afaf"): "\033[38;5;109m", c("#87afd7"): "\033[38;5;110m", c("#87afff"): "\033[38;5;111m",
|
||||
c("#87d700"): "\033[38;5;112m", c("#87d75f"): "\033[38;5;113m", c("#87d787"): "\033[38;5;114m", c("#87d7af"): "\033[38;5;115m",
|
||||
c("#87d7d7"): "\033[38;5;116m", c("#87d7ff"): "\033[38;5;117m", c("#87ff00"): "\033[38;5;118m", c("#87ff5f"): "\033[38;5;119m",
|
||||
c("#87ff87"): "\033[38;5;120m", c("#87ffaf"): "\033[38;5;121m", c("#87ffd7"): "\033[38;5;122m", c("#87ffff"): "\033[38;5;123m",
|
||||
c("#af0000"): "\033[38;5;124m", c("#af005f"): "\033[38;5;125m", c("#af0087"): "\033[38;5;126m", c("#af00af"): "\033[38;5;127m",
|
||||
c("#af00d7"): "\033[38;5;128m", c("#af00ff"): "\033[38;5;129m", c("#af5f00"): "\033[38;5;130m", c("#af5f5f"): "\033[38;5;131m",
|
||||
c("#af5f87"): "\033[38;5;132m", c("#af5faf"): "\033[38;5;133m", c("#af5fd7"): "\033[38;5;134m", c("#af5fff"): "\033[38;5;135m",
|
||||
c("#af8700"): "\033[38;5;136m", c("#af875f"): "\033[38;5;137m", c("#af8787"): "\033[38;5;138m", c("#af87af"): "\033[38;5;139m",
|
||||
c("#af87d7"): "\033[38;5;140m", c("#af87ff"): "\033[38;5;141m", c("#afaf00"): "\033[38;5;142m", c("#afaf5f"): "\033[38;5;143m",
|
||||
c("#afaf87"): "\033[38;5;144m", c("#afafaf"): "\033[38;5;145m", c("#afafd7"): "\033[38;5;146m", c("#afafff"): "\033[38;5;147m",
|
||||
c("#afd700"): "\033[38;5;148m", c("#afd75f"): "\033[38;5;149m", c("#afd787"): "\033[38;5;150m", c("#afd7af"): "\033[38;5;151m",
|
||||
c("#afd7d7"): "\033[38;5;152m", c("#afd7ff"): "\033[38;5;153m", c("#afff00"): "\033[38;5;154m", c("#afff5f"): "\033[38;5;155m",
|
||||
c("#afff87"): "\033[38;5;156m", c("#afffaf"): "\033[38;5;157m", c("#afffd7"): "\033[38;5;158m", c("#afffff"): "\033[38;5;159m",
|
||||
c("#d70000"): "\033[38;5;160m", c("#d7005f"): "\033[38;5;161m", c("#d70087"): "\033[38;5;162m", c("#d700af"): "\033[38;5;163m",
|
||||
c("#d700d7"): "\033[38;5;164m", c("#d700ff"): "\033[38;5;165m", c("#d75f00"): "\033[38;5;166m", c("#d75f5f"): "\033[38;5;167m",
|
||||
c("#d75f87"): "\033[38;5;168m", c("#d75faf"): "\033[38;5;169m", c("#d75fd7"): "\033[38;5;170m", c("#d75fff"): "\033[38;5;171m",
|
||||
c("#d78700"): "\033[38;5;172m", c("#d7875f"): "\033[38;5;173m", c("#d78787"): "\033[38;5;174m", c("#d787af"): "\033[38;5;175m",
|
||||
c("#d787d7"): "\033[38;5;176m", c("#d787ff"): "\033[38;5;177m", c("#d7af00"): "\033[38;5;178m", c("#d7af5f"): "\033[38;5;179m",
|
||||
c("#d7af87"): "\033[38;5;180m", c("#d7afaf"): "\033[38;5;181m", c("#d7afd7"): "\033[38;5;182m", c("#d7afff"): "\033[38;5;183m",
|
||||
c("#d7d700"): "\033[38;5;184m", c("#d7d75f"): "\033[38;5;185m", c("#d7d787"): "\033[38;5;186m", c("#d7d7af"): "\033[38;5;187m",
|
||||
c("#d7d7d7"): "\033[38;5;188m", c("#d7d7ff"): "\033[38;5;189m", c("#d7ff00"): "\033[38;5;190m", c("#d7ff5f"): "\033[38;5;191m",
|
||||
c("#d7ff87"): "\033[38;5;192m", c("#d7ffaf"): "\033[38;5;193m", c("#d7ffd7"): "\033[38;5;194m", c("#d7ffff"): "\033[38;5;195m",
|
||||
c("#ff0000"): "\033[38;5;196m", c("#ff005f"): "\033[38;5;197m", c("#ff0087"): "\033[38;5;198m", c("#ff00af"): "\033[38;5;199m",
|
||||
c("#ff00d7"): "\033[38;5;200m", c("#ff00ff"): "\033[38;5;201m", c("#ff5f00"): "\033[38;5;202m", c("#ff5f5f"): "\033[38;5;203m",
|
||||
c("#ff5f87"): "\033[38;5;204m", c("#ff5faf"): "\033[38;5;205m", c("#ff5fd7"): "\033[38;5;206m", c("#ff5fff"): "\033[38;5;207m",
|
||||
c("#ff8700"): "\033[38;5;208m", c("#ff875f"): "\033[38;5;209m", c("#ff8787"): "\033[38;5;210m", c("#ff87af"): "\033[38;5;211m",
|
||||
c("#ff87d7"): "\033[38;5;212m", c("#ff87ff"): "\033[38;5;213m", c("#ffaf00"): "\033[38;5;214m", c("#ffaf5f"): "\033[38;5;215m",
|
||||
c("#ffaf87"): "\033[38;5;216m", c("#ffafaf"): "\033[38;5;217m", c("#ffafd7"): "\033[38;5;218m", c("#ffafff"): "\033[38;5;219m",
|
||||
c("#ffd700"): "\033[38;5;220m", c("#ffd75f"): "\033[38;5;221m", c("#ffd787"): "\033[38;5;222m", c("#ffd7af"): "\033[38;5;223m",
|
||||
c("#ffd7d7"): "\033[38;5;224m", c("#ffd7ff"): "\033[38;5;225m", c("#ffff00"): "\033[38;5;226m", c("#ffff5f"): "\033[38;5;227m",
|
||||
c("#ffff87"): "\033[38;5;228m", c("#ffffaf"): "\033[38;5;229m", c("#ffffd7"): "\033[38;5;230m", c("#ffffff"): "\033[38;5;231m",
|
||||
c("#080808"): "\033[38;5;232m", c("#121212"): "\033[38;5;233m", c("#1c1c1c"): "\033[38;5;234m", c("#262626"): "\033[38;5;235m",
|
||||
c("#303030"): "\033[38;5;236m", c("#3a3a3a"): "\033[38;5;237m", c("#444444"): "\033[38;5;238m", c("#4e4e4e"): "\033[38;5;239m",
|
||||
c("#585858"): "\033[38;5;240m", c("#626262"): "\033[38;5;241m", c("#6c6c6c"): "\033[38;5;242m", c("#767676"): "\033[38;5;243m",
|
||||
c("#808080"): "\033[38;5;244m", c("#8a8a8a"): "\033[38;5;245m", c("#949494"): "\033[38;5;246m", c("#9e9e9e"): "\033[38;5;247m",
|
||||
c("#a8a8a8"): "\033[38;5;248m", c("#b2b2b2"): "\033[38;5;249m", c("#bcbcbc"): "\033[38;5;250m", c("#c6c6c6"): "\033[38;5;251m",
|
||||
c("#d0d0d0"): "\033[38;5;252m", c("#dadada"): "\033[38;5;253m", c("#e4e4e4"): "\033[38;5;254m", c("#eeeeee"): "\033[38;5;255m",
|
||||
},
|
||||
background: map[chroma.Colour]string{
|
||||
c("#000000"): "\033[48;5;0m", c("#800000"): "\033[48;5;1m", c("#008000"): "\033[48;5;2m", c("#808000"): "\033[48;5;3m",
|
||||
c("#000080"): "\033[48;5;4m", c("#800080"): "\033[48;5;5m", c("#008080"): "\033[48;5;6m", c("#c0c0c0"): "\033[48;5;7m",
|
||||
c("#808080"): "\033[48;5;8m", c("#ff0000"): "\033[48;5;9m", c("#00ff00"): "\033[48;5;10m", c("#ffff00"): "\033[48;5;11m",
|
||||
c("#0000ff"): "\033[48;5;12m", c("#ff00ff"): "\033[48;5;13m", c("#00ffff"): "\033[48;5;14m", c("#ffffff"): "\033[48;5;15m",
|
||||
c("#000000"): "\033[48;5;16m", c("#00005f"): "\033[48;5;17m", c("#000087"): "\033[48;5;18m", c("#0000af"): "\033[48;5;19m",
|
||||
c("#0000d7"): "\033[48;5;20m", c("#0000ff"): "\033[48;5;21m", c("#005f00"): "\033[48;5;22m", c("#005f5f"): "\033[48;5;23m",
|
||||
c("#005f87"): "\033[48;5;24m", c("#005faf"): "\033[48;5;25m", c("#005fd7"): "\033[48;5;26m", c("#005fff"): "\033[48;5;27m",
|
||||
c("#008700"): "\033[48;5;28m", c("#00875f"): "\033[48;5;29m", c("#008787"): "\033[48;5;30m", c("#0087af"): "\033[48;5;31m",
|
||||
c("#0087d7"): "\033[48;5;32m", c("#0087ff"): "\033[48;5;33m", c("#00af00"): "\033[48;5;34m", c("#00af5f"): "\033[48;5;35m",
|
||||
c("#00af87"): "\033[48;5;36m", c("#00afaf"): "\033[48;5;37m", c("#00afd7"): "\033[48;5;38m", c("#00afff"): "\033[48;5;39m",
|
||||
c("#00d700"): "\033[48;5;40m", c("#00d75f"): "\033[48;5;41m", c("#00d787"): "\033[48;5;42m", c("#00d7af"): "\033[48;5;43m",
|
||||
c("#00d7d7"): "\033[48;5;44m", c("#00d7ff"): "\033[48;5;45m", c("#00ff00"): "\033[48;5;46m", c("#00ff5f"): "\033[48;5;47m",
|
||||
c("#00ff87"): "\033[48;5;48m", c("#00ffaf"): "\033[48;5;49m", c("#00ffd7"): "\033[48;5;50m", c("#00ffff"): "\033[48;5;51m",
|
||||
c("#5f0000"): "\033[48;5;52m", c("#5f005f"): "\033[48;5;53m", c("#5f0087"): "\033[48;5;54m", c("#5f00af"): "\033[48;5;55m",
|
||||
c("#5f00d7"): "\033[48;5;56m", c("#5f00ff"): "\033[48;5;57m", c("#5f5f00"): "\033[48;5;58m", c("#5f5f5f"): "\033[48;5;59m",
|
||||
c("#5f5f87"): "\033[48;5;60m", c("#5f5faf"): "\033[48;5;61m", c("#5f5fd7"): "\033[48;5;62m", c("#5f5fff"): "\033[48;5;63m",
|
||||
c("#5f8700"): "\033[48;5;64m", c("#5f875f"): "\033[48;5;65m", c("#5f8787"): "\033[48;5;66m", c("#5f87af"): "\033[48;5;67m",
|
||||
c("#5f87d7"): "\033[48;5;68m", c("#5f87ff"): "\033[48;5;69m", c("#5faf00"): "\033[48;5;70m", c("#5faf5f"): "\033[48;5;71m",
|
||||
c("#5faf87"): "\033[48;5;72m", c("#5fafaf"): "\033[48;5;73m", c("#5fafd7"): "\033[48;5;74m", c("#5fafff"): "\033[48;5;75m",
|
||||
c("#5fd700"): "\033[48;5;76m", c("#5fd75f"): "\033[48;5;77m", c("#5fd787"): "\033[48;5;78m", c("#5fd7af"): "\033[48;5;79m",
|
||||
c("#5fd7d7"): "\033[48;5;80m", c("#5fd7ff"): "\033[48;5;81m", c("#5fff00"): "\033[48;5;82m", c("#5fff5f"): "\033[48;5;83m",
|
||||
c("#5fff87"): "\033[48;5;84m", c("#5fffaf"): "\033[48;5;85m", c("#5fffd7"): "\033[48;5;86m", c("#5fffff"): "\033[48;5;87m",
|
||||
c("#870000"): "\033[48;5;88m", c("#87005f"): "\033[48;5;89m", c("#870087"): "\033[48;5;90m", c("#8700af"): "\033[48;5;91m",
|
||||
c("#8700d7"): "\033[48;5;92m", c("#8700ff"): "\033[48;5;93m", c("#875f00"): "\033[48;5;94m", c("#875f5f"): "\033[48;5;95m",
|
||||
c("#875f87"): "\033[48;5;96m", c("#875faf"): "\033[48;5;97m", c("#875fd7"): "\033[48;5;98m", c("#875fff"): "\033[48;5;99m",
|
||||
c("#878700"): "\033[48;5;100m", c("#87875f"): "\033[48;5;101m", c("#878787"): "\033[48;5;102m", c("#8787af"): "\033[48;5;103m",
|
||||
c("#8787d7"): "\033[48;5;104m", c("#8787ff"): "\033[48;5;105m", c("#87af00"): "\033[48;5;106m", c("#87af5f"): "\033[48;5;107m",
|
||||
c("#87af87"): "\033[48;5;108m", c("#87afaf"): "\033[48;5;109m", c("#87afd7"): "\033[48;5;110m", c("#87afff"): "\033[48;5;111m",
|
||||
c("#87d700"): "\033[48;5;112m", c("#87d75f"): "\033[48;5;113m", c("#87d787"): "\033[48;5;114m", c("#87d7af"): "\033[48;5;115m",
|
||||
c("#87d7d7"): "\033[48;5;116m", c("#87d7ff"): "\033[48;5;117m", c("#87ff00"): "\033[48;5;118m", c("#87ff5f"): "\033[48;5;119m",
|
||||
c("#87ff87"): "\033[48;5;120m", c("#87ffaf"): "\033[48;5;121m", c("#87ffd7"): "\033[48;5;122m", c("#87ffff"): "\033[48;5;123m",
|
||||
c("#af0000"): "\033[48;5;124m", c("#af005f"): "\033[48;5;125m", c("#af0087"): "\033[48;5;126m", c("#af00af"): "\033[48;5;127m",
|
||||
c("#af00d7"): "\033[48;5;128m", c("#af00ff"): "\033[48;5;129m", c("#af5f00"): "\033[48;5;130m", c("#af5f5f"): "\033[48;5;131m",
|
||||
c("#af5f87"): "\033[48;5;132m", c("#af5faf"): "\033[48;5;133m", c("#af5fd7"): "\033[48;5;134m", c("#af5fff"): "\033[48;5;135m",
|
||||
c("#af8700"): "\033[48;5;136m", c("#af875f"): "\033[48;5;137m", c("#af8787"): "\033[48;5;138m", c("#af87af"): "\033[48;5;139m",
|
||||
c("#af87d7"): "\033[48;5;140m", c("#af87ff"): "\033[48;5;141m", c("#afaf00"): "\033[48;5;142m", c("#afaf5f"): "\033[48;5;143m",
|
||||
c("#afaf87"): "\033[48;5;144m", c("#afafaf"): "\033[48;5;145m", c("#afafd7"): "\033[48;5;146m", c("#afafff"): "\033[48;5;147m",
|
||||
c("#afd700"): "\033[48;5;148m", c("#afd75f"): "\033[48;5;149m", c("#afd787"): "\033[48;5;150m", c("#afd7af"): "\033[48;5;151m",
|
||||
c("#afd7d7"): "\033[48;5;152m", c("#afd7ff"): "\033[48;5;153m", c("#afff00"): "\033[48;5;154m", c("#afff5f"): "\033[48;5;155m",
|
||||
c("#afff87"): "\033[48;5;156m", c("#afffaf"): "\033[48;5;157m", c("#afffd7"): "\033[48;5;158m", c("#afffff"): "\033[48;5;159m",
|
||||
c("#d70000"): "\033[48;5;160m", c("#d7005f"): "\033[48;5;161m", c("#d70087"): "\033[48;5;162m", c("#d700af"): "\033[48;5;163m",
|
||||
c("#d700d7"): "\033[48;5;164m", c("#d700ff"): "\033[48;5;165m", c("#d75f00"): "\033[48;5;166m", c("#d75f5f"): "\033[48;5;167m",
|
||||
c("#d75f87"): "\033[48;5;168m", c("#d75faf"): "\033[48;5;169m", c("#d75fd7"): "\033[48;5;170m", c("#d75fff"): "\033[48;5;171m",
|
||||
c("#d78700"): "\033[48;5;172m", c("#d7875f"): "\033[48;5;173m", c("#d78787"): "\033[48;5;174m", c("#d787af"): "\033[48;5;175m",
|
||||
c("#d787d7"): "\033[48;5;176m", c("#d787ff"): "\033[48;5;177m", c("#d7af00"): "\033[48;5;178m", c("#d7af5f"): "\033[48;5;179m",
|
||||
c("#d7af87"): "\033[48;5;180m", c("#d7afaf"): "\033[48;5;181m", c("#d7afd7"): "\033[48;5;182m", c("#d7afff"): "\033[48;5;183m",
|
||||
c("#d7d700"): "\033[48;5;184m", c("#d7d75f"): "\033[48;5;185m", c("#d7d787"): "\033[48;5;186m", c("#d7d7af"): "\033[48;5;187m",
|
||||
c("#d7d7d7"): "\033[48;5;188m", c("#d7d7ff"): "\033[48;5;189m", c("#d7ff00"): "\033[48;5;190m", c("#d7ff5f"): "\033[48;5;191m",
|
||||
c("#d7ff87"): "\033[48;5;192m", c("#d7ffaf"): "\033[48;5;193m", c("#d7ffd7"): "\033[48;5;194m", c("#d7ffff"): "\033[48;5;195m",
|
||||
c("#ff0000"): "\033[48;5;196m", c("#ff005f"): "\033[48;5;197m", c("#ff0087"): "\033[48;5;198m", c("#ff00af"): "\033[48;5;199m",
|
||||
c("#ff00d7"): "\033[48;5;200m", c("#ff00ff"): "\033[48;5;201m", c("#ff5f00"): "\033[48;5;202m", c("#ff5f5f"): "\033[48;5;203m",
|
||||
c("#ff5f87"): "\033[48;5;204m", c("#ff5faf"): "\033[48;5;205m", c("#ff5fd7"): "\033[48;5;206m", c("#ff5fff"): "\033[48;5;207m",
|
||||
c("#ff8700"): "\033[48;5;208m", c("#ff875f"): "\033[48;5;209m", c("#ff8787"): "\033[48;5;210m", c("#ff87af"): "\033[48;5;211m",
|
||||
c("#ff87d7"): "\033[48;5;212m", c("#ff87ff"): "\033[48;5;213m", c("#ffaf00"): "\033[48;5;214m", c("#ffaf5f"): "\033[48;5;215m",
|
||||
c("#ffaf87"): "\033[48;5;216m", c("#ffafaf"): "\033[48;5;217m", c("#ffafd7"): "\033[48;5;218m", c("#ffafff"): "\033[48;5;219m",
|
||||
c("#ffd700"): "\033[48;5;220m", c("#ffd75f"): "\033[48;5;221m", c("#ffd787"): "\033[48;5;222m", c("#ffd7af"): "\033[48;5;223m",
|
||||
c("#ffd7d7"): "\033[48;5;224m", c("#ffd7ff"): "\033[48;5;225m", c("#ffff00"): "\033[48;5;226m", c("#ffff5f"): "\033[48;5;227m",
|
||||
c("#ffff87"): "\033[48;5;228m", c("#ffffaf"): "\033[48;5;229m", c("#ffffd7"): "\033[48;5;230m", c("#ffffff"): "\033[48;5;231m",
|
||||
c("#080808"): "\033[48;5;232m", c("#121212"): "\033[48;5;233m", c("#1c1c1c"): "\033[48;5;234m", c("#262626"): "\033[48;5;235m",
|
||||
c("#303030"): "\033[48;5;236m", c("#3a3a3a"): "\033[48;5;237m", c("#444444"): "\033[48;5;238m", c("#4e4e4e"): "\033[48;5;239m",
|
||||
c("#585858"): "\033[48;5;240m", c("#626262"): "\033[48;5;241m", c("#6c6c6c"): "\033[48;5;242m", c("#767676"): "\033[48;5;243m",
|
||||
c("#808080"): "\033[48;5;244m", c("#8a8a8a"): "\033[48;5;245m", c("#949494"): "\033[48;5;246m", c("#9e9e9e"): "\033[48;5;247m",
|
||||
c("#a8a8a8"): "\033[48;5;248m", c("#b2b2b2"): "\033[48;5;249m", c("#bcbcbc"): "\033[48;5;250m", c("#c6c6c6"): "\033[48;5;251m",
|
||||
c("#d0d0d0"): "\033[48;5;252m", c("#dadada"): "\033[48;5;253m", c("#e4e4e4"): "\033[48;5;254m", c("#eeeeee"): "\033[48;5;255m",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func entryToEscapeSequence(table *ttyTable, entry chroma.StyleEntry) string {
|
||||
out := ""
|
||||
if entry.Bold == chroma.Yes {
|
||||
out += "\033[1m"
|
||||
}
|
||||
if entry.Underline == chroma.Yes {
|
||||
out += "\033[4m"
|
||||
}
|
||||
if entry.Italic == chroma.Yes {
|
||||
out += "\033[3m"
|
||||
}
|
||||
if entry.Colour.IsSet() {
|
||||
out += table.foreground[findClosest(table, entry.Colour)]
|
||||
}
|
||||
if entry.Background.IsSet() {
|
||||
out += table.background[findClosest(table, entry.Background)]
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func findClosest(table *ttyTable, seeking chroma.Colour) chroma.Colour {
|
||||
closestColour := chroma.Colour(0)
|
||||
closest := float64(math.MaxFloat64)
|
||||
for colour := range table.foreground {
|
||||
distance := colour.Distance(seeking)
|
||||
if distance < closest {
|
||||
closest = distance
|
||||
closestColour = colour
|
||||
}
|
||||
}
|
||||
return closestColour
|
||||
}
|
||||
|
||||
func styleToEscapeSequence(table *ttyTable, style *chroma.Style) map[chroma.TokenType]string {
|
||||
style = clearBackground(style)
|
||||
out := map[chroma.TokenType]string{}
|
||||
for _, ttype := range style.Types() {
|
||||
entry := style.Get(ttype)
|
||||
out[ttype] = entryToEscapeSequence(table, entry)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Clear the background colour.
|
||||
func clearBackground(style *chroma.Style) *chroma.Style {
|
||||
builder := style.Builder()
|
||||
bg := builder.Get(chroma.Background)
|
||||
bg.Background = 0
|
||||
bg.NoInherit = true
|
||||
builder.AddEntry(chroma.Background, bg)
|
||||
style, _ = builder.Build()
|
||||
return style
|
||||
}
|
||||
|
||||
type indexedTTYFormatter struct {
|
||||
table *ttyTable
|
||||
}
|
||||
|
||||
func (c *indexedTTYFormatter) Format(w io.Writer, style *chroma.Style, it chroma.Iterator) (err error) {
|
||||
theme := styleToEscapeSequence(c.table, style)
|
||||
for token := it(); token != chroma.EOF; token = it() {
|
||||
// TODO: Cache token lookups?
|
||||
clr, ok := theme[token.Type]
|
||||
if !ok {
|
||||
clr, ok = theme[token.Type.SubCategory()]
|
||||
if !ok {
|
||||
clr = theme[token.Type.Category()]
|
||||
// if !ok {
|
||||
// clr = theme[chroma.InheritStyle]
|
||||
// }
|
||||
}
|
||||
}
|
||||
if clr != "" {
|
||||
fmt.Fprint(w, clr)
|
||||
}
|
||||
fmt.Fprint(w, token.Value)
|
||||
if clr != "" {
|
||||
fmt.Fprintf(w, "\033[0m")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TTY8 is an 8-colour terminal formatter.
|
||||
//
|
||||
// The Lab colour space is used to map RGB values to the most appropriate index colour.
|
||||
var TTY8 = Register("terminal", &indexedTTYFormatter{ttyTables[8]})
|
||||
|
||||
// TTY256 is a 256-colour terminal formatter.
|
||||
//
|
||||
// The Lab colour space is used to map RGB values to the most appropriate index colour.
|
||||
var TTY256 = Register("terminal256", &indexedTTYFormatter{ttyTables[256]})
|
|
@ -0,0 +1,42 @@
|
|||
package formatters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/alecthomas/chroma"
|
||||
)
|
||||
|
||||
// TTY16m is a true-colour terminal formatter.
|
||||
var TTY16m = Register("terminal16m", chroma.FormatterFunc(trueColourFormatter))
|
||||
|
||||
func trueColourFormatter(w io.Writer, style *chroma.Style, it chroma.Iterator) error {
|
||||
style = clearBackground(style)
|
||||
for token := it(); token != chroma.EOF; token = it() {
|
||||
entry := style.Get(token.Type)
|
||||
if !entry.IsZero() {
|
||||
out := ""
|
||||
if entry.Bold == chroma.Yes {
|
||||
out += "\033[1m"
|
||||
}
|
||||
if entry.Underline == chroma.Yes {
|
||||
out += "\033[4m"
|
||||
}
|
||||
if entry.Italic == chroma.Yes {
|
||||
out += "\033[3m"
|
||||
}
|
||||
if entry.Colour.IsSet() {
|
||||
out += fmt.Sprintf("\033[38;2;%d;%d;%dm", entry.Colour.Red(), entry.Colour.Green(), entry.Colour.Blue())
|
||||
}
|
||||
if entry.Background.IsSet() {
|
||||
out += fmt.Sprintf("\033[48;2;%d;%d;%dm", entry.Background.Red(), entry.Background.Green(), entry.Background.Blue())
|
||||
}
|
||||
fmt.Fprint(w, out)
|
||||
}
|
||||
fmt.Fprint(w, token.Value)
|
||||
if !entry.IsZero() {
|
||||
fmt.Fprint(w, "\033[0m")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
module github.com/alecthomas/chroma
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 // indirect
|
||||
github.com/alecthomas/kong v0.2.4
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 // indirect
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964
|
||||
github.com/dlclark/regexp2 v1.2.0
|
||||
github.com/mattn/go-colorable v0.1.6
|
||||
github.com/mattn/go-isatty v0.0.12
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sergi/go-diff v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
|
||||
)
|
|
@ -0,0 +1,36 @@
|
|||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||
github.com/alecthomas/kong v0.2.4 h1:Y0ZBCHAvHhTHw7FFJ2FzCAAG4pkbTgA45nc7BpMhDNk=
|
||||
github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
|
||||
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
@ -0,0 +1,76 @@
|
|||
package chroma
|
||||
|
||||
import "strings"
|
||||
|
||||
// An Iterator across tokens.
|
||||
//
|
||||
// nil will be returned at the end of the Token stream.
|
||||
//
|
||||
// If an error occurs within an Iterator, it may propagate this in a panic. Formatters should recover.
|
||||
type Iterator func() Token
|
||||
|
||||
// Tokens consumes all tokens from the iterator and returns them as a slice.
|
||||
func (i Iterator) Tokens() []Token {
|
||||
var out []Token
|
||||
for t := i(); t != EOF; t = i() {
|
||||
out = append(out, t)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Concaterator concatenates tokens from a series of iterators.
|
||||
func Concaterator(iterators ...Iterator) Iterator {
|
||||
return func() Token {
|
||||
for len(iterators) > 0 {
|
||||
t := iterators[0]()
|
||||
if t != EOF {
|
||||
return t
|
||||
}
|
||||
iterators = iterators[1:]
|
||||
}
|
||||
return EOF
|
||||
}
|
||||
}
|
||||
|
||||
// Literator converts a sequence of literal Tokens into an Iterator.
|
||||
func Literator(tokens ...Token) Iterator {
|
||||
return func() Token {
|
||||
if len(tokens) == 0 {
|
||||
return EOF
|
||||
}
|
||||
token := tokens[0]
|
||||
tokens = tokens[1:]
|
||||
return token
|
||||
}
|
||||
}
|
||||
|
||||
// SplitTokensIntoLines splits tokens containing newlines in two.
|
||||
func SplitTokensIntoLines(tokens []Token) (out [][]Token) {
|
||||
var line []Token // nolint: prealloc
|
||||
for _, token := range tokens {
|
||||
for strings.Contains(token.Value, "\n") {
|
||||
parts := strings.SplitAfterN(token.Value, "\n", 2)
|
||||
// Token becomes the tail.
|
||||
token.Value = parts[1]
|
||||
|
||||
// Append the head to the line and flush the line.
|
||||
clone := token.Clone()
|
||||
clone.Value = parts[0]
|
||||
line = append(line, clone)
|
||||
out = append(out, line)
|
||||
line = nil
|
||||
}
|
||||
line = append(line, token)
|
||||
}
|
||||
if len(line) > 0 {
|
||||
out = append(out, line)
|
||||
}
|
||||
// Strip empty trailing token line.
|
||||
if len(out) > 0 {
|
||||
last := out[len(out)-1]
|
||||
if len(last) == 1 && last[0].Value == "" {
|
||||
out = out[:len(out)-1]
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package chroma
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultOptions = &TokeniseOptions{
|
||||
State: "root",
|
||||
EnsureLF: true,
|
||||
}
|
||||
)
|
||||
|
||||
// Config for a lexer.
|
||||
type Config struct {
|
||||
// Name of the lexer.
|
||||
Name string
|
||||
|
||||
// Shortcuts for the lexer
|
||||
Aliases []string
|
||||
|
||||
// File name globs
|
||||
Filenames []string
|
||||
|
||||
// Secondary file name globs
|
||||
AliasFilenames []string
|
||||
|
||||
// MIME types
|
||||
MimeTypes []string
|
||||
|
||||
// Regex matching is case-insensitive.
|
||||
CaseInsensitive bool
|
||||
|
||||
// Regex matches all characters.
|
||||
DotAll bool
|
||||
|
||||
// Regex does not match across lines ($ matches EOL).
|
||||
//
|
||||
// Defaults to multiline.
|
||||
NotMultiline bool
|
||||
|
||||
// Don't strip leading and trailing newlines from the input.
|
||||
// DontStripNL bool
|
||||
|
||||
// Strip all leading and trailing whitespace from the input
|
||||
// StripAll bool
|
||||
|
||||
// Make sure that the input ends with a newline. This
|
||||
// is required for some lexers that consume input linewise.
|
||||
EnsureNL bool
|
||||
|
||||
// If given and greater than 0, expand tabs in the input.
|
||||
// TabSize int
|
||||
|
||||
// Priority of lexer.
|
||||
//
|
||||
// If this is 0 it will be treated as a default of 1.
|
||||
Priority float32
|
||||
}
|
||||
|
||||
// Token output to formatter.
|
||||
type Token struct {
|
||||
Type TokenType `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
func (t *Token) String() string { return t.Value }
|
||||
func (t *Token) GoString() string { return fmt.Sprintf("&Token{%s, %q}", t.Type, t.Value) }
|
||||
|
||||
// Clone returns a clone of the Token.
|
||||
func (t *Token) Clone() Token {
|
||||
return *t
|
||||
}
|
||||
|
||||
// EOF is returned by lexers at the end of input.
|
||||
var EOF Token
|
||||
|
||||
// TokeniseOptions contains options for tokenisers.
|
||||
type TokeniseOptions struct {
|
||||
// State to start tokenisation in. Defaults to "root".
|
||||
State string
|
||||
// Nested tokenisation.
|
||||
Nested bool
|
||||
|
||||
// If true, all EOLs are converted into LF
|
||||
// by replacing CRLF and CR
|
||||
EnsureLF bool
|
||||
}
|
||||
|
||||
// A Lexer for tokenising source code.
|
||||
type Lexer interface {
|
||||
// Config describing the features of the Lexer.
|
||||
Config() *Config
|
||||
// Tokenise returns an Iterator over tokens in text.
|
||||
Tokenise(options *TokeniseOptions, text string) (Iterator, error)
|
||||
}
|
||||
|
||||
// Lexers is a slice of lexers sortable by name.
|
||||
type Lexers []Lexer
|
||||
|
||||
func (l Lexers) Len() int { return len(l) }
|
||||
func (l Lexers) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
func (l Lexers) Less(i, j int) bool { return l[i].Config().Name < l[j].Config().Name }
|
||||
|
||||
// PrioritisedLexers is a slice of lexers sortable by priority.
|
||||
type PrioritisedLexers []Lexer
|
||||
|
||||
func (l PrioritisedLexers) Len() int { return len(l) }
|
||||
func (l PrioritisedLexers) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
func (l PrioritisedLexers) Less(i, j int) bool {
|
||||
ip := l[i].Config().Priority
|
||||
if ip == 0 {
|
||||
ip = 1
|
||||
}
|
||||
jp := l[j].Config().Priority
|
||||
if jp == 0 {
|
||||
jp = 1
|
||||
}
|
||||
return ip > jp
|
||||
}
|
||||
|
||||
// Analyser determines how appropriate this lexer is for the given text.
|
||||
type Analyser interface {
|
||||
AnalyseText(text string) float32
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
# Lexer tests
|
||||
|
||||
The tests in this directory feed a known input `testdata/<name>.actual` into the parser for `<name>` and check
|
||||
that its output matches `<name>.exported`.
|
||||
|
||||
## Running the tests
|
||||
|
||||
Run the tests as normal:
|
||||
```go
|
||||
go test ./lexers
|
||||
```
|
||||
|
||||
## Update existing tests
|
||||
When you add a new test data file (`*.actual`), you need to regenerate all tests. That's how Chroma creates the `*.expected` test file based on the corresponding lexer.
|
||||
|
||||
To regenerate all tests, type in your terminal:
|
||||
|
||||
```go
|
||||
RECORD=true go test ./lexers
|
||||
```
|
||||
|
||||
This first sets the `RECORD` environment variable to `true`. Then it runs `go test` on the `./lexers` directory of the Chroma project.
|
||||
|
||||
(That environment variable tells Chroma it needs to output test data. After running `go test ./lexers` you can remove or reset that variable.)
|
||||
|
||||
### Windows users
|
||||
Windows users will find that the `RECORD=true go test ./lexers` command fails in both the standard command prompt terminal and in PowerShell.
|
||||
|
||||
Instead we have to perform both steps separately:
|
||||
|
||||
- Set the `RECORD` environment variable to `true`.
|
||||
+ In the regular command prompt window, the `set` command sets an environment variable for the current session: `set RECORD=true`. See [this page](https://superuser.com/questions/212150/how-to-set-env-variable-in-windows-cmd-line) for more.
|
||||
+ In PowerShell, you can use the `$env:RECORD = 'true'` command for that. See [this article](https://mcpmag.com/articles/2019/03/28/environment-variables-in-powershell.aspx) for more.
|
||||
+ You can also make a persistent environment variable by hand in the Windows computer settings. See [this article](https://www.computerhope.com/issues/ch000549.htm) for how.
|
||||
- When the environment variable is set, run `go tests ./lexers`.
|
||||
|
||||
Chroma will now regenerate the test files and print its results to the console window.
|
|
@ -0,0 +1,56 @@
|
|||
package a
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// ABAP lexer.
|
||||
var Abap = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "ABAP",
|
||||
Aliases: []string{"abap"},
|
||||
Filenames: []string{"*.abap", "*.ABAP"},
|
||||
MimeTypes: []string{"text/x-abap"},
|
||||
CaseInsensitive: true,
|
||||
},
|
||||
Rules{
|
||||
"common": {
|
||||
{`\s+`, Text, nil},
|
||||
{`^\*.*$`, CommentSingle, nil},
|
||||
{`\".*?\n`, CommentSingle, nil},
|
||||
{`##\w+`, CommentSpecial, nil},
|
||||
},
|
||||
"variable-names": {
|
||||
{`<\S+>`, NameVariable, nil},
|
||||
{`\w[\w~]*(?:(\[\])|->\*)?`, NameVariable, nil},
|
||||
},
|
||||
"root": {
|
||||
Include("common"),
|
||||
{`CALL\s+(?:BADI|CUSTOMER-FUNCTION|FUNCTION)`, Keyword, nil},
|
||||
{`(CALL\s+(?:DIALOG|SCREEN|SUBSCREEN|SELECTION-SCREEN|TRANSACTION|TRANSFORMATION))\b`, Keyword, nil},
|
||||
{`(FORM|PERFORM)(\s+)(\w+)`, ByGroups(Keyword, Text, NameFunction), nil},
|
||||
{`(PERFORM)(\s+)(\()(\w+)(\))`, ByGroups(Keyword, Text, Punctuation, NameVariable, Punctuation), nil},
|
||||
{`(MODULE)(\s+)(\S+)(\s+)(INPUT|OUTPUT)`, ByGroups(Keyword, Text, NameFunction, Text, Keyword), nil},
|
||||
{`(METHOD)(\s+)([\w~]+)`, ByGroups(Keyword, Text, NameFunction), nil},
|
||||
{`(\s+)([\w\-]+)([=\-]>)([\w\-~]+)`, ByGroups(Text, NameVariable, Operator, NameFunction), nil},
|
||||
{`(?<=(=|-)>)([\w\-~]+)(?=\()`, NameFunction, nil},
|
||||
{`(TEXT)(-)(\d{3})`, ByGroups(Keyword, Punctuation, LiteralNumberInteger), nil},
|
||||
{`(TEXT)(-)(\w{3})`, ByGroups(Keyword, Punctuation, NameVariable), nil},
|
||||
{`(ADD-CORRESPONDING|AUTHORITY-CHECK|CLASS-DATA|CLASS-EVENTS|CLASS-METHODS|CLASS-POOL|DELETE-ADJACENT|DIVIDE-CORRESPONDING|EDITOR-CALL|ENHANCEMENT-POINT|ENHANCEMENT-SECTION|EXIT-COMMAND|FIELD-GROUPS|FIELD-SYMBOLS|FUNCTION-POOL|INTERFACE-POOL|INVERTED-DATE|LOAD-OF-PROGRAM|LOG-POINT|MESSAGE-ID|MOVE-CORRESPONDING|MULTIPLY-CORRESPONDING|NEW-LINE|NEW-PAGE|NEW-SECTION|NO-EXTENSION|OUTPUT-LENGTH|PRINT-CONTROL|SELECT-OPTIONS|START-OF-SELECTION|SUBTRACT-CORRESPONDING|SYNTAX-CHECK|SYSTEM-EXCEPTIONS|TYPE-POOL|TYPE-POOLS|NO-DISPLAY)\b`, Keyword, nil},
|
||||
{`(?<![-\>])(CREATE\s+(PUBLIC|PRIVATE|DATA|OBJECT)|(PUBLIC|PRIVATE|PROTECTED)\s+SECTION|(TYPE|LIKE)\s+((LINE\s+OF|REF\s+TO|(SORTED|STANDARD|HASHED)\s+TABLE\s+OF))?|FROM\s+(DATABASE|MEMORY)|CALL\s+METHOD|(GROUP|ORDER) BY|HAVING|SEPARATED BY|GET\s+(BADI|BIT|CURSOR|DATASET|LOCALE|PARAMETER|PF-STATUS|(PROPERTY|REFERENCE)\s+OF|RUN\s+TIME|TIME\s+(STAMP)?)?|SET\s+(BIT|BLANK\s+LINES|COUNTRY|CURSOR|DATASET|EXTENDED\s+CHECK|HANDLER|HOLD\s+DATA|LANGUAGE|LEFT\s+SCROLL-BOUNDARY|LOCALE|MARGIN|PARAMETER|PF-STATUS|PROPERTY\s+OF|RUN\s+TIME\s+(ANALYZER|CLOCK\s+RESOLUTION)|SCREEN|TITLEBAR|UPADTE\s+TASK\s+LOCAL|USER-COMMAND)|CONVERT\s+((INVERTED-)?DATE|TIME|TIME\s+STAMP|TEXT)|(CLOSE|OPEN)\s+(DATASET|CURSOR)|(TO|FROM)\s+(DATA BUFFER|INTERNAL TABLE|MEMORY ID|DATABASE|SHARED\s+(MEMORY|BUFFER))|DESCRIBE\s+(DISTANCE\s+BETWEEN|FIELD|LIST|TABLE)|FREE\s(MEMORY|OBJECT)?|PROCESS\s+(BEFORE\s+OUTPUT|AFTER\s+INPUT|ON\s+(VALUE-REQUEST|HELP-REQUEST))|AT\s+(LINE-SELECTION|USER-COMMAND|END\s+OF|NEW)|AT\s+SELECTION-SCREEN(\s+(ON(\s+(BLOCK|(HELP|VALUE)-REQUEST\s+FOR|END\s+OF|RADIOBUTTON\s+GROUP))?|OUTPUT))?|SELECTION-SCREEN:?\s+((BEGIN|END)\s+OF\s+((TABBED\s+)?BLOCK|LINE|SCREEN)|COMMENT|FUNCTION\s+KEY|INCLUDE\s+BLOCKS|POSITION|PUSHBUTTON|SKIP|ULINE)|LEAVE\s+(LIST-PROCESSING|PROGRAM|SCREEN|TO LIST-PROCESSING|TO TRANSACTION)(ENDING|STARTING)\s+AT|FORMAT\s+(COLOR|INTENSIFIED|INVERSE|HOTSPOT|INPUT|FRAMES|RESET)|AS\s+(CHECKBOX|SUBSCREEN|WINDOW)|WITH\s+(((NON-)?UNIQUE)?\s+KEY|FRAME)|(BEGIN|END)\s+OF|DELETE(\s+ADJACENT\s+DUPLICATES\sFROM)?|COMPARING(\s+ALL\s+FIELDS)?|(INSERT|APPEND)(\s+INITIAL\s+LINE\s+(IN)?TO|\s+LINES\s+OF)?|IN\s+((BYTE|CHARACTER)\s+MODE|PROGRAM)|END-OF-(DEFINITION|PAGE|SELECTION)|WITH\s+FRAME(\s+TITLE)|(REPLACE|FIND)\s+((FIRST|ALL)\s+OCCURRENCES?\s+OF\s+)?(SUBSTRING|REGEX)?|MATCH\s+(LENGTH|COUNT|LINE|OFFSET)|(RESPECTING|IGNORING)\s+CASE|IN\s+UPDATE\s+TASK|(SOURCE|RESULT)\s+(XML)?|REFERENCE\s+INTO|AND\s+(MARK|RETURN)|CLIENT\s+SPECIFIED|CORRESPONDING\s+FIELDS\s+OF|IF\s+FOUND|FOR\s+EVENT|INHERITING\s+FROM|LEAVE\s+TO\s+SCREEN|LOOP\s+AT\s+(SCREEN)?|LOWER\s+CASE|MATCHCODE\s+OBJECT|MODIF\s+ID|MODIFY\s+SCREEN|NESTING\s+LEVEL|NO\s+INTERVALS|OF\s+STRUCTURE|RADIOBUTTON\s+GROUP|RANGE\s+OF|REF\s+TO|SUPPRESS DIALOG|TABLE\s+OF|UPPER\s+CASE|TRANSPORTING\s+NO\s+FIELDS|VALUE\s+CHECK|VISIBLE\s+LENGTH|HEADER\s+LINE|COMMON\s+PART)\b`, Keyword, nil},
|
||||
{`(^|(?<=(\s|\.)))(ABBREVIATED|ABSTRACT|ADD|ALIASES|ALIGN|ALPHA|ASSERT|AS|ASSIGN(ING)?|AT(\s+FIRST)?|BACK|BLOCK|BREAK-POINT|CASE|CATCH|CHANGING|CHECK|CLASS|CLEAR|COLLECT|COLOR|COMMIT|CREATE|COMMUNICATION|COMPONENTS?|COMPUTE|CONCATENATE|CONDENSE|CONSTANTS|CONTEXTS|CONTINUE|CONTROLS|COUNTRY|CURRENCY|DATA|DATE|DECIMALS|DEFAULT|DEFINE|DEFINITION|DEFERRED|DEMAND|DETAIL|DIRECTORY|DIVIDE|DO|DUMMY|ELSE(IF)?|ENDAT|ENDCASE|ENDCATCH|ENDCLASS|ENDDO|ENDFORM|ENDFUNCTION|ENDIF|ENDINTERFACE|ENDLOOP|ENDMETHOD|ENDMODULE|ENDSELECT|ENDTRY|ENDWHILE|ENHANCEMENT|EVENTS|EXACT|EXCEPTIONS?|EXIT|EXPONENT|EXPORT|EXPORTING|EXTRACT|FETCH|FIELDS?|FOR|FORM|FORMAT|FREE|FROM|FUNCTION|HIDE|ID|IF|IMPORT|IMPLEMENTATION|IMPORTING|IN|INCLUDE|INCLUDING|INDEX|INFOTYPES|INITIALIZATION|INTERFACE|INTERFACES|INTO|LANGUAGE|LEAVE|LENGTH|LINES|LOAD|LOCAL|JOIN|KEY|NEXT|MAXIMUM|MESSAGE|METHOD[S]?|MINIMUM|MODULE|MODIFIER|MODIFY|MOVE|MULTIPLY|NODES|NUMBER|OBLIGATORY|OBJECT|OF|OFF|ON|OTHERS|OVERLAY|PACK|PAD|PARAMETERS|PERCENTAGE|POSITION|PROGRAM|PROVIDE|PUBLIC|PUT|PF\d\d|RAISE|RAISING|RANGES?|READ|RECEIVE|REDEFINITION|REFRESH|REJECT|REPORT|RESERVE|RESUME|RETRY|RETURN|RETURNING|RIGHT|ROLLBACK|REPLACE|SCROLL|SEARCH|SELECT|SHIFT|SIGN|SINGLE|SIZE|SKIP|SORT|SPLIT|STATICS|STOP|STYLE|SUBMATCHES|SUBMIT|SUBTRACT|SUM(?!\()|SUMMARY|SUMMING|SUPPLY|TABLE|TABLES|TIMESTAMP|TIMES?|TIMEZONE|TITLE|\??TO|TOP-OF-PAGE|TRANSFER|TRANSLATE|TRY|TYPES|ULINE|UNDER|UNPACK|UPDATE|USING|VALUE|VALUES|VIA|VARYING|VARY|WAIT|WHEN|WHERE|WIDTH|WHILE|WITH|WINDOW|WRITE|XSD|ZERO)\b`, Keyword, nil},
|
||||
{`(abs|acos|asin|atan|boolc|boolx|bit_set|char_off|charlen|ceil|cmax|cmin|condense|contains|contains_any_of|contains_any_not_of|concat_lines_of|cos|cosh|count|count_any_of|count_any_not_of|dbmaxlen|distance|escape|exp|find|find_end|find_any_of|find_any_not_of|floor|frac|from_mixed|insert|lines|log|log10|match|matches|nmax|nmin|numofchar|repeat|replace|rescale|reverse|round|segment|shift_left|shift_right|sign|sin|sinh|sqrt|strlen|substring|substring_after|substring_from|substring_before|substring_to|tan|tanh|to_upper|to_lower|to_mixed|translate|trunc|xstrlen)(\()\b`, ByGroups(NameBuiltin, Punctuation), nil},
|
||||
{`&[0-9]`, Name, nil},
|
||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
||||
{`(?<=(\s|.))(AND|OR|EQ|NE|GT|LT|GE|LE|CO|CN|CA|NA|CS|NOT|NS|CP|NP|BYTE-CO|BYTE-CN|BYTE-CA|BYTE-NA|BYTE-CS|BYTE-NS|IS\s+(NOT\s+)?(INITIAL|ASSIGNED|REQUESTED|BOUND))\b`, OperatorWord, nil},
|
||||
Include("variable-names"),
|
||||
{`[?*<>=\-+&]`, Operator, nil},
|
||||
{`'(''|[^'])*'`, LiteralStringSingle, nil},
|
||||
{"`([^`])*`", LiteralStringSingle, nil},
|
||||
{`([|}])([^{}|]*?)([|{])`, ByGroups(Punctuation, LiteralStringSingle, Punctuation), nil},
|
||||
{`[/;:()\[\],.]`, Punctuation, nil},
|
||||
{`(!)(\w+)`, ByGroups(Operator, Name), nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,38 @@
|
|||
package a
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Abnf lexer.
|
||||
var Abnf = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "ABNF",
|
||||
Aliases: []string{"abnf"},
|
||||
Filenames: []string{"*.abnf"},
|
||||
MimeTypes: []string{"text/x-abnf"},
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`;.*$`, CommentSingle, nil},
|
||||
{`(%[si])?"[^"]*"`, Literal, nil},
|
||||
{`%b[01]+\-[01]+\b`, Literal, nil},
|
||||
{`%b[01]+(\.[01]+)*\b`, Literal, nil},
|
||||
{`%d[0-9]+\-[0-9]+\b`, Literal, nil},
|
||||
{`%d[0-9]+(\.[0-9]+)*\b`, Literal, nil},
|
||||
{`%x[0-9a-fA-F]+\-[0-9a-fA-F]+\b`, Literal, nil},
|
||||
{`%x[0-9a-fA-F]+(\.[0-9a-fA-F]+)*\b`, Literal, nil},
|
||||
{`\b[0-9]+\*[0-9]+`, Operator, nil},
|
||||
{`\b[0-9]+\*`, Operator, nil},
|
||||
{`\b[0-9]+`, Operator, nil},
|
||||
{`\*`, Operator, nil},
|
||||
{Words(``, `\b`, `ALPHA`, `BIT`, `CHAR`, `CR`, `CRLF`, `CTL`, `DIGIT`, `DQUOTE`, `HEXDIG`, `HTAB`, `LF`, `LWSP`, `OCTET`, `SP`, `VCHAR`, `WSP`), Keyword, nil},
|
||||
{`[a-zA-Z][a-zA-Z0-9-]+\b`, NameClass, nil},
|
||||
{`(=/|=|/)`, Operator, nil},
|
||||
{`[\[\]()]`, Punctuation, nil},
|
||||
{`\s+`, Text, nil},
|
||||
{`.`, Text, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,39 @@
|
|||
package a
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Actionscript lexer.
|
||||
var Actionscript = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "ActionScript",
|
||||
Aliases: []string{"as", "actionscript"},
|
||||
Filenames: []string{"*.as"},
|
||||
MimeTypes: []string{"application/x-actionscript", "text/x-actionscript", "text/actionscript"},
|
||||
NotMultiline: true,
|
||||
DotAll: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`\s+`, Text, nil},
|
||||
{`//.*?\n`, CommentSingle, nil},
|
||||
{`/\*.*?\*/`, CommentMultiline, nil},
|
||||
{`/(\\\\|\\/|[^/\n])*/[gim]*`, LiteralStringRegex, nil},
|
||||
{`[~^*!%&<>|+=:;,/?\\-]+`, Operator, nil},
|
||||
{`[{}\[\]();.]+`, Punctuation, nil},
|
||||
{Words(``, `\b`, `case`, `default`, `for`, `each`, `in`, `while`, `do`, `break`, `return`, `continue`, `if`, `else`, `throw`, `try`, `catch`, `var`, `with`, `new`, `typeof`, `arguments`, `instanceof`, `this`, `switch`), Keyword, nil},
|
||||
{Words(``, `\b`, `class`, `public`, `final`, `internal`, `native`, `override`, `private`, `protected`, `static`, `import`, `extends`, `implements`, `interface`, `intrinsic`, `return`, `super`, `dynamic`, `function`, `const`, `get`, `namespace`, `package`, `set`), KeywordDeclaration, nil},
|
||||
{`(true|false|null|NaN|Infinity|-Infinity|undefined|Void)\b`, KeywordConstant, nil},
|
||||
{Words(``, `\b`, `Accessibility`, `AccessibilityProperties`, `ActionScriptVersion`, `ActivityEvent`, `AntiAliasType`, `ApplicationDomain`, `AsBroadcaster`, `Array`, `AsyncErrorEvent`, `AVM1Movie`, `BevelFilter`, `Bitmap`, `BitmapData`, `BitmapDataChannel`, `BitmapFilter`, `BitmapFilterQuality`, `BitmapFilterType`, `BlendMode`, `BlurFilter`, `Boolean`, `ByteArray`, `Camera`, `Capabilities`, `CapsStyle`, `Class`, `Color`, `ColorMatrixFilter`, `ColorTransform`, `ContextMenu`, `ContextMenuBuiltInItems`, `ContextMenuEvent`, `ContextMenuItem`, `ConvultionFilter`, `CSMSettings`, `DataEvent`, `Date`, `DefinitionError`, `DeleteObjectSample`, `Dictionary`, `DisplacmentMapFilter`, `DisplayObject`, `DisplacmentMapFilterMode`, `DisplayObjectContainer`, `DropShadowFilter`, `Endian`, `EOFError`, `Error`, `ErrorEvent`, `EvalError`, `Event`, `EventDispatcher`, `EventPhase`, `ExternalInterface`, `FileFilter`, `FileReference`, `FileReferenceList`, `FocusDirection`, `FocusEvent`, `Font`, `FontStyle`, `FontType`, `FrameLabel`, `FullScreenEvent`, `Function`, `GlowFilter`, `GradientBevelFilter`, `GradientGlowFilter`, `GradientType`, `Graphics`, `GridFitType`, `HTTPStatusEvent`, `IBitmapDrawable`, `ID3Info`, `IDataInput`, `IDataOutput`, `IDynamicPropertyOutputIDynamicPropertyWriter`, `IEventDispatcher`, `IExternalizable`, `IllegalOperationError`, `IME`, `IMEConversionMode`, `IMEEvent`, `int`, `InteractiveObject`, `InterpolationMethod`, `InvalidSWFError`, `InvokeEvent`, `IOError`, `IOErrorEvent`, `JointStyle`, `Key`, `Keyboard`, `KeyboardEvent`, `KeyLocation`, `LineScaleMode`, `Loader`, `LoaderContext`, `LoaderInfo`, `LoadVars`, `LocalConnection`, `Locale`, `Math`, `Matrix`, `MemoryError`, `Microphone`, `MorphShape`, `Mouse`, `MouseEvent`, `MovieClip`, `MovieClipLoader`, `Namespace`, `NetConnection`, `NetStatusEvent`, `NetStream`, `NewObjectSample`, `Number`, `Object`, `ObjectEncoding`, `PixelSnapping`, `Point`, `PrintJob`, `PrintJobOptions`, `PrintJobOrientation`, `ProgressEvent`, `Proxy`, `QName`, `RangeError`, `Rectangle`, `ReferenceError`, `RegExp`, `Responder`, `Sample`, `Scene`, `ScriptTimeoutError`, `Security`, `SecurityDomain`, `SecurityError`, `SecurityErrorEvent`, `SecurityPanel`, `Selection`, `Shape`, `SharedObject`, `SharedObjectFlushStatus`, `SimpleButton`, `Socket`, `Sound`, `SoundChannel`, `SoundLoaderContext`, `SoundMixer`, `SoundTransform`, `SpreadMethod`, `Sprite`, `StackFrame`, `StackOverflowError`, `Stage`, `StageAlign`, `StageDisplayState`, `StageQuality`, `StageScaleMode`, `StaticText`, `StatusEvent`, `String`, `StyleSheet`, `SWFVersion`, `SyncEvent`, `SyntaxError`, `System`, `TextColorType`, `TextField`, `TextFieldAutoSize`, `TextFieldType`, `TextFormat`, `TextFormatAlign`, `TextLineMetrics`, `TextRenderer`, `TextSnapshot`, `Timer`, `TimerEvent`, `Transform`, `TypeError`, `uint`, `URIError`, `URLLoader`, `URLLoaderDataFormat`, `URLRequest`, `URLRequestHeader`, `URLRequestMethod`, `URLStream`, `URLVariabeles`, `VerifyError`, `Video`, `XML`, `XMLDocument`, `XMLList`, `XMLNode`, `XMLNodeType`, `XMLSocket`, `XMLUI`), NameBuiltin, nil},
|
||||
{Words(``, `\b`, `decodeURI`, `decodeURIComponent`, `encodeURI`, `escape`, `eval`, `isFinite`, `isNaN`, `isXMLName`, `clearInterval`, `fscommand`, `getTimer`, `getURL`, `getVersion`, `parseFloat`, `parseInt`, `setInterval`, `trace`, `updateAfterEvent`, `unescape`), NameFunction, nil},
|
||||
{`[$a-zA-Z_]\w*`, NameOther, nil},
|
||||
{`[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?`, LiteralNumberFloat, nil},
|
||||
{`0x[0-9a-f]+`, LiteralNumberHex, nil},
|
||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
||||
{`"(\\\\|\\"|[^"])*"`, LiteralStringDouble, nil},
|
||||
{`'(\\\\|\\'|[^'])*'`, LiteralStringSingle, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,56 @@
|
|||
package a
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Actionscript 3 lexer.
|
||||
var Actionscript3 = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "ActionScript 3",
|
||||
Aliases: []string{"as3", "actionscript3"},
|
||||
Filenames: []string{"*.as"},
|
||||
MimeTypes: []string{"application/x-actionscript3", "text/x-actionscript3", "text/actionscript3"},
|
||||
DotAll: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`\s+`, Text, nil},
|
||||
{`(function\s+)([$a-zA-Z_]\w*)(\s*)(\()`, ByGroups(KeywordDeclaration, NameFunction, Text, Operator), Push("funcparams")},
|
||||
{`(var|const)(\s+)([$a-zA-Z_]\w*)(\s*)(:)(\s*)([$a-zA-Z_]\w*(?:\.<\w+>)?)`, ByGroups(KeywordDeclaration, Text, Name, Text, Punctuation, Text, KeywordType), nil},
|
||||
{`(import|package)(\s+)((?:[$a-zA-Z_]\w*|\.)+)(\s*)`, ByGroups(Keyword, Text, NameNamespace, Text), nil},
|
||||
{`(new)(\s+)([$a-zA-Z_]\w*(?:\.<\w+>)?)(\s*)(\()`, ByGroups(Keyword, Text, KeywordType, Text, Operator), nil},
|
||||
{`//.*?\n`, CommentSingle, nil},
|
||||
{`/\*.*?\*/`, CommentMultiline, nil},
|
||||
{`/(\\\\|\\/|[^\n])*/[gisx]*`, LiteralStringRegex, nil},
|
||||
{`(\.)([$a-zA-Z_]\w*)`, ByGroups(Operator, NameAttribute), nil},
|
||||
{`(case|default|for|each|in|while|do|break|return|continue|if|else|throw|try|catch|with|new|typeof|arguments|instanceof|this|switch|import|include|as|is)\b`, Keyword, nil},
|
||||
{`(class|public|final|internal|native|override|private|protected|static|import|extends|implements|interface|intrinsic|return|super|dynamic|function|const|get|namespace|package|set)\b`, KeywordDeclaration, nil},
|
||||
{`(true|false|null|NaN|Infinity|-Infinity|undefined|void)\b`, KeywordConstant, nil},
|
||||
{`(decodeURI|decodeURIComponent|encodeURI|escape|eval|isFinite|isNaN|isXMLName|clearInterval|fscommand|getTimer|getURL|getVersion|isFinite|parseFloat|parseInt|setInterval|trace|updateAfterEvent|unescape)\b`, NameFunction, nil},
|
||||
{`[$a-zA-Z_]\w*`, Name, nil},
|
||||
{`[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?`, LiteralNumberFloat, nil},
|
||||
{`0x[0-9a-f]+`, LiteralNumberHex, nil},
|
||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
||||
{`"(\\\\|\\"|[^"])*"`, LiteralStringDouble, nil},
|
||||
{`'(\\\\|\\'|[^'])*'`, LiteralStringSingle, nil},
|
||||
{`[~^*!%&<>|+=:;,/?\\{}\[\]().-]+`, Operator, nil},
|
||||
},
|
||||
"funcparams": {
|
||||
{`\s+`, Text, nil},
|
||||
{`(\s*)(\.\.\.)?([$a-zA-Z_]\w*)(\s*)(:)(\s*)([$a-zA-Z_]\w*(?:\.<\w+>)?|\*)(\s*)`, ByGroups(Text, Punctuation, Name, Text, Operator, Text, KeywordType, Text), Push("defval")},
|
||||
{`\)`, Operator, Push("type")},
|
||||
},
|
||||
"type": {
|
||||
{`(\s*)(:)(\s*)([$a-zA-Z_]\w*(?:\.<\w+>)?|\*)`, ByGroups(Text, Operator, Text, KeywordType), Pop(2)},
|
||||
{`\s+`, Text, Pop(2)},
|
||||
Default(Pop(2)),
|
||||
},
|
||||
"defval": {
|
||||
{`(=)(\s*)([^(),]+)(\s*)(,?)`, ByGroups(Operator, Text, UsingSelf("root"), Text, Operator), Pop(1)},
|
||||
{`,`, Operator, Pop(1)},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,114 @@
|
|||
package a
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Ada lexer.
|
||||
var Ada = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Ada",
|
||||
Aliases: []string{"ada", "ada95", "ada2005"},
|
||||
Filenames: []string{"*.adb", "*.ads", "*.ada"},
|
||||
MimeTypes: []string{"text/x-ada"},
|
||||
CaseInsensitive: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`[^\S\n]+`, Text, nil},
|
||||
{`--.*?\n`, CommentSingle, nil},
|
||||
{`[^\S\n]+`, Text, nil},
|
||||
{`function|procedure|entry`, KeywordDeclaration, Push("subprogram")},
|
||||
{`(subtype|type)(\s+)(\w+)`, ByGroups(KeywordDeclaration, Text, KeywordType), Push("type_def")},
|
||||
{`task|protected`, KeywordDeclaration, nil},
|
||||
{`(subtype)(\s+)`, ByGroups(KeywordDeclaration, Text), nil},
|
||||
{`(end)(\s+)`, ByGroups(KeywordReserved, Text), Push("end")},
|
||||
{`(pragma)(\s+)(\w+)`, ByGroups(KeywordReserved, Text, CommentPreproc), nil},
|
||||
{`(true|false|null)\b`, KeywordConstant, nil},
|
||||
{Words(``, `\b`, `Address`, `Byte`, `Boolean`, `Character`, `Controlled`, `Count`, `Cursor`, `Duration`, `File_Mode`, `File_Type`, `Float`, `Generator`, `Integer`, `Long_Float`, `Long_Integer`, `Long_Long_Float`, `Long_Long_Integer`, `Natural`, `Positive`, `Reference_Type`, `Short_Float`, `Short_Integer`, `Short_Short_Float`, `Short_Short_Integer`, `String`, `Wide_Character`, `Wide_String`), KeywordType, nil},
|
||||
{`(and(\s+then)?|in|mod|not|or(\s+else)|rem)\b`, OperatorWord, nil},
|
||||
{`generic|private`, KeywordDeclaration, nil},
|
||||
{`package`, KeywordDeclaration, Push("package")},
|
||||
{`array\b`, KeywordReserved, Push("array_def")},
|
||||
{`(with|use)(\s+)`, ByGroups(KeywordNamespace, Text), Push("import")},
|
||||
{`(\w+)(\s*)(:)(\s*)(constant)`, ByGroups(NameConstant, Text, Punctuation, Text, KeywordReserved), nil},
|
||||
{`<<\w+>>`, NameLabel, nil},
|
||||
{`(\w+)(\s*)(:)(\s*)(declare|begin|loop|for|while)`, ByGroups(NameLabel, Text, Punctuation, Text, KeywordReserved), nil},
|
||||
{Words(`\b`, `\b`, `abort`, `abs`, `abstract`, `accept`, `access`, `aliased`, `all`, `array`, `at`, `begin`, `body`, `case`, `constant`, `declare`, `delay`, `delta`, `digits`, `do`, `else`, `elsif`, `end`, `entry`, `exception`, `exit`, `interface`, `for`, `goto`, `if`, `is`, `limited`, `loop`, `new`, `null`, `of`, `or`, `others`, `out`, `overriding`, `pragma`, `protected`, `raise`, `range`, `record`, `renames`, `requeue`, `return`, `reverse`, `select`, `separate`, `subtype`, `synchronized`, `task`, `tagged`, `terminate`, `then`, `type`, `until`, `when`, `while`, `xor`), KeywordReserved, nil},
|
||||
{`"[^"]*"`, LiteralString, nil},
|
||||
Include("attribute"),
|
||||
Include("numbers"),
|
||||
{`'[^']'`, LiteralStringChar, nil},
|
||||
{`(\w+)(\s*|[(,])`, ByGroups(Name, UsingSelf("root")), nil},
|
||||
{`(<>|=>|:=|[()|:;,.'])`, Punctuation, nil},
|
||||
{`[*<>+=/&-]`, Operator, nil},
|
||||
{`\n+`, Text, nil},
|
||||
},
|
||||
"numbers": {
|
||||
{`[0-9_]+#[0-9a-f]+#`, LiteralNumberHex, nil},
|
||||
{`[0-9_]+\.[0-9_]*`, LiteralNumberFloat, nil},
|
||||
{`[0-9_]+`, LiteralNumberInteger, nil},
|
||||
},
|
||||
"attribute": {
|
||||
{`(')(\w+)`, ByGroups(Punctuation, NameAttribute), nil},
|
||||
},
|
||||
"subprogram": {
|
||||
{`\(`, Punctuation, Push("#pop", "formal_part")},
|
||||
{`;`, Punctuation, Pop(1)},
|
||||
{`is\b`, KeywordReserved, Pop(1)},
|
||||
{`"[^"]+"|\w+`, NameFunction, nil},
|
||||
Include("root"),
|
||||
},
|
||||
"end": {
|
||||
{`(if|case|record|loop|select)`, KeywordReserved, nil},
|
||||
{`"[^"]+"|[\w.]+`, NameFunction, nil},
|
||||
{`\s+`, Text, nil},
|
||||
{`;`, Punctuation, Pop(1)},
|
||||
},
|
||||
"type_def": {
|
||||
{`;`, Punctuation, Pop(1)},
|
||||
{`\(`, Punctuation, Push("formal_part")},
|
||||
{`with|and|use`, KeywordReserved, nil},
|
||||
{`array\b`, KeywordReserved, Push("#pop", "array_def")},
|
||||
{`record\b`, KeywordReserved, Push("record_def")},
|
||||
{`(null record)(;)`, ByGroups(KeywordReserved, Punctuation), Pop(1)},
|
||||
Include("root"),
|
||||
},
|
||||
"array_def": {
|
||||
{`;`, Punctuation, Pop(1)},
|
||||
{`(\w+)(\s+)(range)`, ByGroups(KeywordType, Text, KeywordReserved), nil},
|
||||
Include("root"),
|
||||
},
|
||||
"record_def": {
|
||||
{`end record`, KeywordReserved, Pop(1)},
|
||||
Include("root"),
|
||||
},
|
||||
"import": {
|
||||
{`[\w.]+`, NameNamespace, Pop(1)},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"formal_part": {
|
||||
{`\)`, Punctuation, Pop(1)},
|
||||
{`\w+`, NameVariable, nil},
|
||||
{`,|:[^=]`, Punctuation, nil},
|
||||
{`(in|not|null|out|access)\b`, KeywordReserved, nil},
|
||||
Include("root"),
|
||||
},
|
||||
"package": {
|
||||
{`body`, KeywordDeclaration, nil},
|
||||
{`is\s+new|renames`, KeywordReserved, nil},
|
||||
{`is`, KeywordReserved, Pop(1)},
|
||||
{`;`, Punctuation, Pop(1)},
|
||||
{`\(`, Punctuation, Push("package_instantiation")},
|
||||
{`([\w.]+)`, NameClass, nil},
|
||||
Include("root"),
|
||||
},
|
||||
"package_instantiation": {
|
||||
{`("[^"]+"|\w+)(\s+)(=>)`, ByGroups(NameVariable, Text, Punctuation), nil},
|
||||
{`[\w.\'"]`, Text, nil},
|
||||
{`\)`, Punctuation, Pop(1)},
|
||||
Include("root"),
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,42 @@
|
|||
package a
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Angular2 lexer.
|
||||
var Angular2 = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Angular2",
|
||||
Aliases: []string{"ng2"},
|
||||
Filenames: []string{},
|
||||
MimeTypes: []string{},
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`[^{([*#]+`, Other, nil},
|
||||
{`(\{\{)(\s*)`, ByGroups(CommentPreproc, Text), Push("ngExpression")},
|
||||
{`([([]+)([\w:.-]+)([\])]+)(\s*)(=)(\s*)`, ByGroups(Punctuation, NameAttribute, Punctuation, Text, Operator, Text), Push("attr")},
|
||||
{`([([]+)([\w:.-]+)([\])]+)(\s*)`, ByGroups(Punctuation, NameAttribute, Punctuation, Text), nil},
|
||||
{`([*#])([\w:.-]+)(\s*)(=)(\s*)`, ByGroups(Punctuation, NameAttribute, Punctuation, Operator), Push("attr")},
|
||||
{`([*#])([\w:.-]+)(\s*)`, ByGroups(Punctuation, NameAttribute, Punctuation), nil},
|
||||
},
|
||||
"ngExpression": {
|
||||
{`\s+(\|\s+)?`, Text, nil},
|
||||
{`\}\}`, CommentPreproc, Pop(1)},
|
||||
{`:?(true|false)`, LiteralStringBoolean, nil},
|
||||
{`:?"(\\\\|\\"|[^"])*"`, LiteralStringDouble, nil},
|
||||
{`:?'(\\\\|\\'|[^'])*'`, LiteralStringSingle, nil},
|
||||
{`[0-9](\.[0-9]*)?(eE[+-][0-9])?[flFLdD]?|0[xX][0-9a-fA-F]+[Ll]?`, LiteralNumber, nil},
|
||||
{`[a-zA-Z][\w-]*(\(.*\))?`, NameVariable, nil},
|
||||
{`\.[\w-]+(\(.*\))?`, NameVariable, nil},
|
||||
{`(\?)(\s*)([^}\s]+)(\s*)(:)(\s*)([^}\s]+)(\s*)`, ByGroups(Operator, Text, LiteralString, Text, Operator, Text, LiteralString, Text), nil},
|
||||
},
|
||||
"attr": {
|
||||
{`".*?"`, LiteralString, Pop(1)},
|
||||
{`'.*?'`, LiteralString, Pop(1)},
|
||||
{`[^\s>]+`, LiteralString, Pop(1)},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,101 @@
|
|||
package a
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// ANTLR lexer.
|
||||
var ANTLR = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "ANTLR",
|
||||
Aliases: []string{"antlr"},
|
||||
Filenames: []string{},
|
||||
MimeTypes: []string{},
|
||||
},
|
||||
Rules{
|
||||
"whitespace": {
|
||||
{`\s+`, TextWhitespace, nil},
|
||||
},
|
||||
"comments": {
|
||||
{`//.*$`, Comment, nil},
|
||||
{`/\*(.|\n)*?\*/`, Comment, nil},
|
||||
},
|
||||
"root": {
|
||||
Include("whitespace"),
|
||||
Include("comments"),
|
||||
{`(lexer|parser|tree)?(\s*)(grammar\b)(\s*)([A-Za-z]\w*)(;)`, ByGroups(Keyword, TextWhitespace, Keyword, TextWhitespace, NameClass, Punctuation), nil},
|
||||
{`options\b`, Keyword, Push("options")},
|
||||
{`tokens\b`, Keyword, Push("tokens")},
|
||||
{`(scope)(\s*)([A-Za-z]\w*)(\s*)(\{)`, ByGroups(Keyword, TextWhitespace, NameVariable, TextWhitespace, Punctuation), Push("action")},
|
||||
{`(catch|finally)\b`, Keyword, Push("exception")},
|
||||
{`(@[A-Za-z]\w*)(\s*)(::)?(\s*)([A-Za-z]\w*)(\s*)(\{)`, ByGroups(NameLabel, TextWhitespace, Punctuation, TextWhitespace, NameLabel, TextWhitespace, Punctuation), Push("action")},
|
||||
{`((?:protected|private|public|fragment)\b)?(\s*)([A-Za-z]\w*)(!)?`, ByGroups(Keyword, TextWhitespace, NameLabel, Punctuation), Push("rule-alts", "rule-prelims")},
|
||||
},
|
||||
"exception": {
|
||||
{`\n`, TextWhitespace, Pop(1)},
|
||||
{`\s`, TextWhitespace, nil},
|
||||
Include("comments"),
|
||||
{`\[`, Punctuation, Push("nested-arg-action")},
|
||||
{`\{`, Punctuation, Push("action")},
|
||||
},
|
||||
"rule-prelims": {
|
||||
Include("whitespace"),
|
||||
Include("comments"),
|
||||
{`returns\b`, Keyword, nil},
|
||||
{`\[`, Punctuation, Push("nested-arg-action")},
|
||||
{`\{`, Punctuation, Push("action")},
|
||||
{`(throws)(\s+)([A-Za-z]\w*)`, ByGroups(Keyword, TextWhitespace, NameLabel), nil},
|
||||
{`(,)(\s*)([A-Za-z]\w*)`, ByGroups(Punctuation, TextWhitespace, NameLabel), nil},
|
||||
{`options\b`, Keyword, Push("options")},
|
||||
{`(scope)(\s+)(\{)`, ByGroups(Keyword, TextWhitespace, Punctuation), Push("action")},
|
||||
{`(scope)(\s+)([A-Za-z]\w*)(\s*)(;)`, ByGroups(Keyword, TextWhitespace, NameLabel, TextWhitespace, Punctuation), nil},
|
||||
{`(@[A-Za-z]\w*)(\s*)(\{)`, ByGroups(NameLabel, TextWhitespace, Punctuation), Push("action")},
|
||||
{`:`, Punctuation, Pop(1)},
|
||||
},
|
||||
"rule-alts": {
|
||||
Include("whitespace"),
|
||||
Include("comments"),
|
||||
{`options\b`, Keyword, Push("options")},
|
||||
{`:`, Punctuation, nil},
|
||||
{`'(\\\\|\\'|[^'])*'`, LiteralString, nil},
|
||||
{`"(\\\\|\\"|[^"])*"`, LiteralString, nil},
|
||||
{`<<([^>]|>[^>])>>`, LiteralString, nil},
|
||||
{`\$?[A-Z_]\w*`, NameConstant, nil},
|
||||
{`\$?[a-z_]\w*`, NameVariable, nil},
|
||||
{`(\+|\||->|=>|=|\(|\)|\.\.|\.|\?|\*|\^|!|\#|~)`, Operator, nil},
|
||||
{`,`, Punctuation, nil},
|
||||
{`\[`, Punctuation, Push("nested-arg-action")},
|
||||
{`\{`, Punctuation, Push("action")},
|
||||
{`;`, Punctuation, Pop(1)},
|
||||
},
|
||||
"tokens": {
|
||||
Include("whitespace"),
|
||||
Include("comments"),
|
||||
{`\{`, Punctuation, nil},
|
||||
{`([A-Z]\w*)(\s*)(=)?(\s*)(\'(?:\\\\|\\\'|[^\']*)\')?(\s*)(;)`, ByGroups(NameLabel, TextWhitespace, Punctuation, TextWhitespace, LiteralString, TextWhitespace, Punctuation), nil},
|
||||
{`\}`, Punctuation, Pop(1)},
|
||||
},
|
||||
"options": {
|
||||
Include("whitespace"),
|
||||
Include("comments"),
|
||||
{`\{`, Punctuation, nil},
|
||||
{`([A-Za-z]\w*)(\s*)(=)(\s*)([A-Za-z]\w*|\'(?:\\\\|\\\'|[^\']*)\'|[0-9]+|\*)(\s*)(;)`, ByGroups(NameVariable, TextWhitespace, Punctuation, TextWhitespace, Text, TextWhitespace, Punctuation), nil},
|
||||
{`\}`, Punctuation, Pop(1)},
|
||||
},
|
||||
"action": {
|
||||
{`([^${}\'"/\\]+|"(\\\\|\\"|[^"])*"|'(\\\\|\\'|[^'])*'|//.*$\n?|/\*(.|\n)*?\*/|/(?!\*)(\\\\|\\/|[^/])*/|\\(?!%)|/)+`, Other, nil},
|
||||
{`(\\)(%)`, ByGroups(Punctuation, Other), nil},
|
||||
{`(\$[a-zA-Z]+)(\.?)(text|value)?`, ByGroups(NameVariable, Punctuation, NameProperty), nil},
|
||||
{`\{`, Punctuation, Push()},
|
||||
{`\}`, Punctuation, Pop(1)},
|
||||
},
|
||||
"nested-arg-action": {
|
||||
{`([^$\[\]\'"/]+|"(\\\\|\\"|[^"])*"|'(\\\\|\\'|[^'])*'|//.*$\n?|/\*(.|\n)*?\*/|/(?!\*)(\\\\|\\/|[^/])*/|/)+`, Other, nil},
|
||||
{`\[`, Punctuation, Push()},
|
||||
{`\]`, Punctuation, Pop(1)},
|
||||
{`(\$[a-zA-Z]+)(\.?)(text|value)?`, ByGroups(NameVariable, Punctuation, NameProperty), nil},
|
||||
{`(\\\\|\\\]|\\\[|[^\[\]])+`, Other, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,38 @@
|
|||
package a
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Apacheconf lexer.
|
||||
var Apacheconf = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "ApacheConf",
|
||||
Aliases: []string{"apacheconf", "aconf", "apache"},
|
||||
Filenames: []string{".htaccess", "apache.conf", "apache2.conf"},
|
||||
MimeTypes: []string{"text/x-apacheconf"},
|
||||
CaseInsensitive: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`\s+`, Text, nil},
|
||||
{`(#.*?)$`, Comment, nil},
|
||||
{`(<[^\s>]+)(?:(\s+)(.*?))?(>)`, ByGroups(NameTag, Text, LiteralString, NameTag), nil},
|
||||
{`([a-z]\w*)(\s+)`, ByGroups(NameBuiltin, Text), Push("value")},
|
||||
{`\.+`, Text, nil},
|
||||
},
|
||||
"value": {
|
||||
{`\\\n`, Text, nil},
|
||||
{`$`, Text, Pop(1)},
|
||||
{`\\`, Text, nil},
|
||||
{`[^\S\n]+`, Text, nil},
|
||||
{`\d+\.\d+\.\d+\.\d+(?:/\d+)?`, LiteralNumber, nil},
|
||||
{`\d+`, LiteralNumber, nil},
|
||||
{`/([a-z0-9][\w./-]+)`, LiteralStringOther, nil},
|
||||
{`(on|off|none|any|all|double|email|dns|min|minimal|os|productonly|full|emerg|alert|crit|error|warn|notice|info|debug|registry|script|inetd|standalone|user|group)\b`, Keyword, nil},
|
||||
{`"([^"\\]*(?:\\.[^"\\]*)*)"`, LiteralStringDouble, nil},
|
||||
{`[^\s"\\]+`, Text, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,36 @@
|
|||
package a
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Apl lexer.
|
||||
var Apl = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "APL",
|
||||
Aliases: []string{"apl"},
|
||||
Filenames: []string{"*.apl"},
|
||||
MimeTypes: []string{},
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`\s+`, Text, nil},
|
||||
{`[⍝#].*$`, CommentSingle, nil},
|
||||
{`\'((\'\')|[^\'])*\'`, LiteralStringSingle, nil},
|
||||
{`"(("")|[^"])*"`, LiteralStringDouble, nil},
|
||||
{`[⋄◇()]`, Punctuation, nil},
|
||||
{`[\[\];]`, LiteralStringRegex, nil},
|
||||
{`⎕[A-Za-zΔ∆⍙][A-Za-zΔ∆⍙_¯0-9]*`, NameFunction, nil},
|
||||
{`[A-Za-zΔ∆⍙][A-Za-zΔ∆⍙_¯0-9]*`, NameVariable, nil},
|
||||
{`¯?(0[Xx][0-9A-Fa-f]+|[0-9]*\.?[0-9]+([Ee][+¯]?[0-9]+)?|¯|∞)([Jj]¯?(0[Xx][0-9A-Fa-f]+|[0-9]*\.?[0-9]+([Ee][+¯]?[0-9]+)?|¯|∞))?`, LiteralNumber, nil},
|
||||
{`[\.\\/⌿⍀¨⍣⍨⍠⍤∘]`, NameAttribute, nil},
|
||||
{`[+\-×÷⌈⌊∣|⍳?*⍟○!⌹<≤=>≥≠≡≢∊⍷∪∩~∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⌷⍋⍒⊤⊥⍕⍎⊣⊢⍁⍂≈⌸⍯↗]`, Operator, nil},
|
||||
{`⍬`, NameConstant, nil},
|
||||
{`[⎕⍞]`, NameVariableGlobal, nil},
|
||||
{`[←→]`, KeywordDeclaration, nil},
|
||||
{`[⍺⍵⍶⍹∇:]`, NameBuiltinPseudo, nil},
|
||||
{`[{}]`, KeywordType, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,55 @@
|
|||
package a
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Applescript lexer.
|
||||
var Applescript = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "AppleScript",
|
||||
Aliases: []string{"applescript"},
|
||||
Filenames: []string{"*.applescript"},
|
||||
MimeTypes: []string{},
|
||||
DotAll: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`\s+`, Text, nil},
|
||||
{`¬\n`, LiteralStringEscape, nil},
|
||||
{`'s\s+`, Text, nil},
|
||||
{`(--|#).*?$`, Comment, nil},
|
||||
{`\(\*`, CommentMultiline, Push("comment")},
|
||||
{`[(){}!,.:]`, Punctuation, nil},
|
||||
{`(«)([^»]+)(»)`, ByGroups(Text, NameBuiltin, Text), nil},
|
||||
{`\b((?:considering|ignoring)\s*)(application responses|case|diacriticals|hyphens|numeric strings|punctuation|white space)`, ByGroups(Keyword, NameBuiltin), nil},
|
||||
{`(-|\*|\+|&|≠|>=?|<=?|=|≥|≤|/|÷|\^)`, Operator, nil},
|
||||
{`\b(and|or|is equal|equals|(is )?equal to|is not|isn't|isn't equal( to)?|is not equal( to)?|doesn't equal|does not equal|(is )?greater than|comes after|is not less than or equal( to)?|isn't less than or equal( to)?|(is )?less than|comes before|is not greater than or equal( to)?|isn't greater than or equal( to)?|(is )?greater than or equal( to)?|is not less than|isn't less than|does not come before|doesn't come before|(is )?less than or equal( to)?|is not greater than|isn't greater than|does not come after|doesn't come after|starts? with|begins? with|ends? with|contains?|does not contain|doesn't contain|is in|is contained by|is not in|is not contained by|isn't contained by|div|mod|not|(a )?(ref( to)?|reference to)|is|does)\b`, OperatorWord, nil},
|
||||
{`^(\s*(?:on|end)\s+)(zoomed|write to file|will zoom|will show|will select tab view item|will resize( sub views)?|will resign active|will quit|will pop up|will open|will move|will miniaturize|will hide|will finish launching|will display outline cell|will display item cell|will display cell|will display browser cell|will dismiss|will close|will become active|was miniaturized|was hidden|update toolbar item|update parameters|update menu item|shown|should zoom|should selection change|should select tab view item|should select row|should select item|should select column|should quit( after last window closed)?|should open( untitled)?|should expand item|should end editing|should collapse item|should close|should begin editing|selection changing|selection changed|selected tab view item|scroll wheel|rows changed|right mouse up|right mouse dragged|right mouse down|resized( sub views)?|resigned main|resigned key|resigned active|read from file|prepare table drop|prepare table drag|prepare outline drop|prepare outline drag|prepare drop|plugin loaded|parameters updated|panel ended|opened|open untitled|number of rows|number of items|number of browser rows|moved|mouse up|mouse moved|mouse exited|mouse entered|mouse dragged|mouse down|miniaturized|load data representation|launched|keyboard up|keyboard down|items changed|item value changed|item value|item expandable|idle|exposed|end editing|drop|drag( (entered|exited|updated))?|double clicked|document nib name|dialog ended|deminiaturized|data representation|conclude drop|column resized|column moved|column clicked|closed|clicked toolbar item|clicked|choose menu item|child of item|changed|change item value|change cell value|cell value changed|cell value|bounds changed|begin editing|became main|became key|awake from nib|alert ended|activated|action|accept table drop|accept outline drop)`, ByGroups(Keyword, NameFunction), nil},
|
||||
{`^(\s*)(in|on|script|to)(\s+)`, ByGroups(Text, Keyword, Text), nil},
|
||||
{`\b(as )(alias |application |boolean |class |constant |date |file |integer |list |number |POSIX file |real |record |reference |RGB color |script |text |unit types|(?:Unicode )?text|string)\b`, ByGroups(Keyword, NameClass), nil},
|
||||
{`\b(AppleScript|current application|false|linefeed|missing value|pi|quote|result|return|space|tab|text item delimiters|true|version)\b`, NameConstant, nil},
|
||||
{`\b(ASCII (character|number)|activate|beep|choose URL|choose application|choose color|choose file( name)?|choose folder|choose from list|choose remote application|clipboard info|close( access)?|copy|count|current date|delay|delete|display (alert|dialog)|do shell script|duplicate|exists|get eof|get volume settings|info for|launch|list (disks|folder)|load script|log|make|mount volume|new|offset|open( (for access|location))?|path to|print|quit|random number|read|round|run( script)?|say|scripting components|set (eof|the clipboard to|volume)|store script|summarize|system attribute|system info|the clipboard|time to GMT|write|quoted form)\b`, NameBuiltin, nil},
|
||||
{`\b(considering|else|error|exit|from|if|ignoring|in|repeat|tell|then|times|to|try|until|using terms from|while|with|with timeout( of)?|with transaction|by|continue|end|its?|me|my|return|of|as)\b`, Keyword, nil},
|
||||
{`\b(global|local|prop(erty)?|set|get)\b`, Keyword, nil},
|
||||
{`\b(but|put|returning|the)\b`, NameBuiltin, nil},
|
||||
{`\b(attachment|attribute run|character|day|month|paragraph|word|year)s?\b`, NameBuiltin, nil},
|
||||
{`\b(about|above|against|apart from|around|aside from|at|below|beneath|beside|between|for|given|instead of|on|onto|out of|over|since)\b`, NameBuiltin, nil},
|
||||
{`\b(accepts arrow key|action method|active|alignment|allowed identifiers|allows branch selection|allows column reordering|allows column resizing|allows column selection|allows customization|allows editing text attributes|allows empty selection|allows mixed state|allows multiple selection|allows reordering|allows undo|alpha( value)?|alternate image|alternate increment value|alternate title|animation delay|associated file name|associated object|auto completes|auto display|auto enables items|auto repeat|auto resizes( outline column)?|auto save expanded items|auto save name|auto save table columns|auto saves configuration|auto scroll|auto sizes all columns to fit|auto sizes cells|background color|bezel state|bezel style|bezeled|border rect|border type|bordered|bounds( rotation)?|box type|button returned|button type|can choose directories|can choose files|can draw|can hide|cell( (background color|size|type))?|characters|class|click count|clicked( data)? column|clicked data item|clicked( data)? row|closeable|collating|color( (mode|panel))|command key down|configuration|content(s| (size|view( margins)?))?|context|continuous|control key down|control size|control tint|control view|controller visible|coordinate system|copies( on scroll)?|corner view|current cell|current column|current( field)? editor|current( menu)? item|current row|current tab view item|data source|default identifiers|delta (x|y|z)|destination window|directory|display mode|displayed cell|document( (edited|rect|view))?|double value|dragged column|dragged distance|dragged items|draws( cell)? background|draws grid|dynamically scrolls|echos bullets|edge|editable|edited( data)? column|edited data item|edited( data)? row|enabled|enclosing scroll view|ending page|error handling|event number|event type|excluded from windows menu|executable path|expanded|fax number|field editor|file kind|file name|file type|first responder|first visible column|flipped|floating|font( panel)?|formatter|frameworks path|frontmost|gave up|grid color|has data items|has horizontal ruler|has horizontal scroller|has parent data item|has resize indicator|has shadow|has sub menu|has vertical ruler|has vertical scroller|header cell|header view|hidden|hides when deactivated|highlights by|horizontal line scroll|horizontal page scroll|horizontal ruler view|horizontally resizable|icon image|id|identifier|ignores multiple clicks|image( (alignment|dims when disabled|frame style|scaling))?|imports graphics|increment value|indentation per level|indeterminate|index|integer value|intercell spacing|item height|key( (code|equivalent( modifier)?|window))?|knob thickness|label|last( visible)? column|leading offset|leaf|level|line scroll|loaded|localized sort|location|loop mode|main( (bunde|menu|window))?|marker follows cell|matrix mode|maximum( content)? size|maximum visible columns|menu( form representation)?|miniaturizable|miniaturized|minimized image|minimized title|minimum column width|minimum( content)? size|modal|modified|mouse down state|movie( (controller|file|rect))?|muted|name|needs display|next state|next text|number of tick marks|only tick mark values|opaque|open panel|option key down|outline table column|page scroll|pages across|pages down|palette label|pane splitter|parent data item|parent window|pasteboard|path( (names|separator))?|playing|plays every frame|plays selection only|position|preferred edge|preferred type|pressure|previous text|prompt|properties|prototype cell|pulls down|rate|released when closed|repeated|requested print time|required file type|resizable|resized column|resource path|returns records|reuses columns|rich text|roll over|row height|rulers visible|save panel|scripts path|scrollable|selectable( identifiers)?|selected cell|selected( data)? columns?|selected data items?|selected( data)? rows?|selected item identifier|selection by rect|send action on arrow key|sends action when done editing|separates columns|separator item|sequence number|services menu|shared frameworks path|shared support path|sheet|shift key down|shows alpha|shows state by|size( mode)?|smart insert delete enabled|sort case sensitivity|sort column|sort order|sort type|sorted( data rows)?|sound|source( mask)?|spell checking enabled|starting page|state|string value|sub menu|super menu|super view|tab key traverses cells|tab state|tab type|tab view|table view|tag|target( printer)?|text color|text container insert|text container origin|text returned|tick mark position|time stamp|title(d| (cell|font|height|position|rect))?|tool tip|toolbar|trailing offset|transparent|treat packages as directories|truncated labels|types|unmodified characters|update views|use sort indicator|user defaults|uses data source|uses ruler|uses threaded animation|uses title from previous column|value wraps|version|vertical( (line scroll|page scroll|ruler view))?|vertically resizable|view|visible( document rect)?|volume|width|window|windows menu|wraps|zoomable|zoomed)\b`, NameAttribute, nil},
|
||||
{`\b(action cell|alert reply|application|box|browser( cell)?|bundle|button( cell)?|cell|clip view|color well|color-panel|combo box( item)?|control|data( (cell|column|item|row|source))?|default entry|dialog reply|document|drag info|drawer|event|font(-panel)?|formatter|image( (cell|view))?|matrix|menu( item)?|item|movie( view)?|open-panel|outline view|panel|pasteboard|plugin|popup button|progress indicator|responder|save-panel|scroll view|secure text field( cell)?|slider|sound|split view|stepper|tab view( item)?|table( (column|header cell|header view|view))|text( (field( cell)?|view))?|toolbar( item)?|user-defaults|view|window)s?\b`, NameBuiltin, nil},
|
||||
{`\b(animate|append|call method|center|close drawer|close panel|display|display alert|display dialog|display panel|go|hide|highlight|increment|item for|load image|load movie|load nib|load panel|load sound|localized string|lock focus|log|open drawer|path for|pause|perform action|play|register|resume|scroll|select( all)?|show|size to fit|start|step back|step forward|stop|synchronize|unlock focus|update)\b`, NameBuiltin, nil},
|
||||
{`\b((in )?back of|(in )?front of|[0-9]+(st|nd|rd|th)|first|second|third|fourth|fifth|sixth|seventh|eighth|ninth|tenth|after|back|before|behind|every|front|index|last|middle|some|that|through|thru|where|whose)\b`, NameBuiltin, nil},
|
||||
{`"(\\\\|\\"|[^"])*"`, LiteralStringDouble, nil},
|
||||
{`\b([a-zA-Z]\w*)\b`, NameVariable, nil},
|
||||
{`[-+]?(\d+\.\d*|\d*\.\d+)(E[-+][0-9]+)?`, LiteralNumberFloat, nil},
|
||||
{`[-+]?\d+`, LiteralNumberInteger, nil},
|
||||
},
|
||||
"comment": {
|
||||
{`\(\*`, CommentMultiline, Push()},
|
||||
{`\*\)`, CommentMultiline, Pop(1)},
|
||||
{`[^*(]+`, CommentMultiline, nil},
|
||||
{`[*(]`, CommentMultiline, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,110 @@
|
|||
package a
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Arduino lexer.
|
||||
var Arduino = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Arduino",
|
||||
Aliases: []string{"arduino"},
|
||||
Filenames: []string{"*.ino"},
|
||||
MimeTypes: []string{"text/x-arduino"},
|
||||
EnsureNL: true,
|
||||
},
|
||||
Rules{
|
||||
"statements": {
|
||||
{Words(``, `\b`, `catch`, `const_cast`, `delete`, `dynamic_cast`, `explicit`, `export`, `friend`, `mutable`, `namespace`, `new`, `operator`, `private`, `protected`, `public`, `reinterpret_cast`, `restrict`, `static_cast`, `template`, `this`, `throw`, `throws`, `try`, `typeid`, `typename`, `using`, `virtual`, `constexpr`, `nullptr`, `decltype`, `thread_local`, `alignas`, `alignof`, `static_assert`, `noexcept`, `override`, `final`), Keyword, nil},
|
||||
{`char(16_t|32_t)\b`, KeywordType, nil},
|
||||
{`(class)\b`, ByGroups(Keyword, Text), Push("classname")},
|
||||
{`(R)(")([^\\()\s]{,16})(\()((?:.|\n)*?)(\)\3)(")`, ByGroups(LiteralStringAffix, LiteralString, LiteralStringDelimiter, LiteralStringDelimiter, LiteralString, LiteralStringDelimiter, LiteralString), nil},
|
||||
{`(u8|u|U)(")`, ByGroups(LiteralStringAffix, LiteralString), Push("string")},
|
||||
{`(L?)(")`, ByGroups(LiteralStringAffix, LiteralString), Push("string")},
|
||||
{`(L?)(')(\\.|\\[0-7]{1,3}|\\x[a-fA-F0-9]{1,2}|[^\\\'\n])(')`, ByGroups(LiteralStringAffix, LiteralStringChar, LiteralStringChar, LiteralStringChar), nil},
|
||||
{`(\d+\.\d*|\.\d+|\d+)[eE][+-]?\d+[LlUu]*`, LiteralNumberFloat, nil},
|
||||
{`(\d+\.\d*|\.\d+|\d+[fF])[fF]?`, LiteralNumberFloat, nil},
|
||||
{`0x[0-9a-fA-F]+[LlUu]*`, LiteralNumberHex, nil},
|
||||
{`0[0-7]+[LlUu]*`, LiteralNumberOct, nil},
|
||||
{`\d+[LlUu]*`, LiteralNumberInteger, nil},
|
||||
{`\*/`, Error, nil},
|
||||
{`[~!%^&*+=|?:<>/-]`, Operator, nil},
|
||||
{`[()\[\],.]`, Punctuation, nil},
|
||||
{Words(``, `\b`, `asm`, `auto`, `break`, `case`, `const`, `continue`, `default`, `do`, `else`, `enum`, `extern`, `for`, `goto`, `if`, `register`, `restricted`, `return`, `sizeof`, `static`, `struct`, `switch`, `typedef`, `union`, `volatile`, `while`), Keyword, nil},
|
||||
{`(_Bool|_Complex|_Imaginary|array|atomic_bool|atomic_char|atomic_int|atomic_llong|atomic_long|atomic_schar|atomic_short|atomic_uchar|atomic_uint|atomic_ullong|atomic_ulong|atomic_ushort|auto|bool|boolean|BooleanVariables|Byte|byte|Char|char|char16_t|char32_t|class|complex|Const|const|const_cast|delete|double|dynamic_cast|enum|explicit|extern|Float|float|friend|inline|Int|int|int16_t|int32_t|int64_t|int8_t|Long|long|new|NULL|null|operator|private|PROGMEM|protected|public|register|reinterpret_cast|short|signed|sizeof|Static|static|static_cast|String|struct|typedef|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|virtual|Void|void|Volatile|volatile|word)\b`, KeywordType, nil},
|
||||
// Start of: Arduino-specific syntax
|
||||
{`(and|final|If|Loop|loop|not|or|override|setup|Setup|throw|try|xor)\b`, Keyword, nil}, // Addition to keywords already defined by C++
|
||||
{`(ANALOG_MESSAGE|BIN|CHANGE|DEC|DEFAULT|DIGITAL_MESSAGE|EXTERNAL|FALLING|FIRMATA_STRING|HALF_PI|HEX|HIGH|INPUT|INPUT_PULLUP|INTERNAL|INTERNAL1V1|INTERNAL1V1|INTERNAL2V56|INTERNAL2V56|LED_BUILTIN|LED_BUILTIN_RX|LED_BUILTIN_TX|LOW|LSBFIRST|MSBFIRST|OCT|OUTPUT|PI|REPORT_ANALOG|REPORT_DIGITAL|RISING|SET_PIN_MODE|SYSEX_START|SYSTEM_RESET|TWO_PI)\b`, KeywordConstant, nil},
|
||||
{`(boolean|const|byte|word|string|String|array)\b`, NameVariable, nil},
|
||||
{`(Keyboard|KeyboardController|MouseController|SoftwareSerial|EthernetServer|EthernetClient|LiquidCrystal|RobotControl|GSMVoiceCall|EthernetUDP|EsploraTFT|HttpClient|RobotMotor|WiFiClient|GSMScanner|FileSystem|Scheduler|GSMServer|YunClient|YunServer|IPAddress|GSMClient|GSMModem|Keyboard|Ethernet|Console|GSMBand|Esplora|Stepper|Process|WiFiUDP|GSM_SMS|Mailbox|USBHost|Firmata|PImage|Client|Server|GSMPIN|FileIO|Bridge|Serial|EEPROM|Stream|Mouse|Audio|Servo|File|Task|GPRS|WiFi|Wire|TFT|GSM|SPI|SD)\b`, NameClass, nil},
|
||||
{`(abs|Abs|accept|ACos|acos|acosf|addParameter|analogRead|AnalogRead|analogReadResolution|AnalogReadResolution|analogReference|AnalogReference|analogWrite|AnalogWrite|analogWriteResolution|AnalogWriteResolution|answerCall|asin|ASin|asinf|atan|ATan|atan2|ATan2|atan2f|atanf|attach|attached|attachGPRS|attachInterrupt|AttachInterrupt|autoscroll|available|availableForWrite|background|beep|begin|beginPacket|beginSD|beginSMS|beginSpeaker|beginTFT|beginTransmission|beginWrite|bit|Bit|BitClear|bitClear|bitRead|BitRead|bitSet|BitSet|BitWrite|bitWrite|blink|blinkVersion|BSSID|buffer|byte|cbrt|cbrtf|Ceil|ceil|ceilf|changePIN|char|charAt|checkPIN|checkPUK|checkReg|circle|cityNameRead|cityNameWrite|clear|clearScreen|click|close|compareTo|compassRead|concat|config|connect|connected|constrain|Constrain|copysign|copysignf|cos|Cos|cosf|cosh|coshf|countryNameRead|countryNameWrite|createChar|cursor|debugPrint|degrees|Delay|delay|DelayMicroseconds|delayMicroseconds|detach|DetachInterrupt|detachInterrupt|DigitalPinToInterrupt|digitalPinToInterrupt|DigitalRead|digitalRead|DigitalWrite|digitalWrite|disconnect|display|displayLogos|drawBMP|drawCompass|encryptionType|end|endPacket|endSMS|endsWith|endTransmission|endWrite|equals|equalsIgnoreCase|exists|exitValue|Exp|exp|expf|fabs|fabsf|fdim|fdimf|fill|find|findUntil|float|floor|Floor|floorf|flush|fma|fmaf|fmax|fmaxf|fmin|fminf|fmod|fmodf|gatewayIP|get|getAsynchronously|getBand|getButton|getBytes|getCurrentCarrier|getIMEI|getKey|getModifiers|getOemKey|getPINUsed|getResult|getSignalStrength|getSocket|getVoiceCallStatus|getXChange|getYChange|hangCall|height|highByte|HighByte|home|hypot|hypotf|image|indexOf|int|interrupts|IPAddress|IRread|isActionDone|isAlpha|isAlphaNumeric|isAscii|isControl|isDigit|isDirectory|isfinite|isGraph|isHexadecimalDigit|isinf|isListening|isLowerCase|isnan|isPIN|isPressed|isPrintable|isPunct|isSpace|isUpperCase|isValid|isWhitespace|keyboardRead|keyPressed|keyReleased|knobRead|lastIndexOf|ldexp|ldexpf|leftToRight|length|line|lineFollowConfig|listen|listenOnLocalhost|loadImage|localIP|log|Log|log10|log10f|logf|long|lowByte|LowByte|lrint|lrintf|lround|lroundf|macAddress|maintain|map|Map|Max|max|messageAvailable|Micros|micros|millis|Millis|Min|min|mkdir|motorsStop|motorsWrite|mouseDragged|mouseMoved|mousePressed|mouseReleased|move|noAutoscroll|noBlink|noBuffer|noCursor|noDisplay|noFill|noInterrupts|NoInterrupts|noListenOnLocalhost|noStroke|noTone|NoTone|onReceive|onRequest|open|openNextFile|overflow|parseCommand|parseFloat|parseInt|parsePacket|pauseMode|peek|PinMode|pinMode|playFile|playMelody|point|pointTo|position|Pow|pow|powf|prepare|press|print|printFirmwareVersion|println|printVersion|process|processInput|PulseIn|pulseIn|pulseInLong|PulseInLong|put|radians|random|Random|randomSeed|RandomSeed|read|readAccelerometer|readBlue|readButton|readBytes|readBytesUntil|readGreen|readJoystickButton|readJoystickSwitch|readJoystickX|readJoystickY|readLightSensor|readMessage|readMicrophone|readNetworks|readRed|readSlider|readString|readStringUntil|readTemperature|ready|rect|release|releaseAll|remoteIP|remoteNumber|remotePort|remove|replace|requestFrom|retrieveCallingNumber|rewindDirectory|rightToLeft|rmdir|robotNameRead|robotNameWrite|round|roundf|RSSI|run|runAsynchronously|running|runShellCommand|runShellCommandAsynchronously|scanNetworks|scrollDisplayLeft|scrollDisplayRight|seek|sendAnalog|sendDigitalPortPair|sendDigitalPorts|sendString|sendSysex|Serial_Available|Serial_Begin|Serial_End|Serial_Flush|Serial_Peek|Serial_Print|Serial_Println|Serial_Read|serialEvent|setBand|setBitOrder|setCharAt|setClockDivider|setCursor|setDataMode|setDNS|setFirmwareVersion|setMode|setPINUsed|setSpeed|setTextSize|setTimeout|ShiftIn|shiftIn|ShiftOut|shiftOut|shutdown|signbit|sin|Sin|sinf|sinh|sinhf|size|sizeof|Sq|sq|Sqrt|sqrt|sqrtf|SSID|startLoop|startsWith|step|stop|stroke|subnetMask|substring|switchPIN|tan|Tan|tanf|tanh|tanhf|tempoWrite|text|toCharArray|toInt|toLowerCase|tone|Tone|toUpperCase|transfer|trim|trunc|truncf|tuneWrite|turn|updateIR|userNameRead|userNameWrite|voiceCall|waitContinue|width|WiFiServer|word|write|writeBlue|writeGreen|writeJSON|writeMessage|writeMicroseconds|writeRed|writeRGB|yield|Yield)\b`, NameFunction, nil},
|
||||
// End of: Arduino-specific syntax
|
||||
{Words(``, `\b`, `inline`, `_inline`, `__inline`, `naked`, `restrict`, `thread`, `typename`), KeywordReserved, nil},
|
||||
{`(__m(128i|128d|128|64))\b`, KeywordReserved, nil},
|
||||
{Words(`__`, `\b`, `asm`, `int8`, `based`, `except`, `int16`, `stdcall`, `cdecl`, `fastcall`, `int32`, `declspec`, `finally`, `int64`, `try`, `leave`, `wchar_t`, `w64`, `unaligned`, `raise`, `noop`, `identifier`, `forceinline`, `assume`), KeywordReserved, nil},
|
||||
{`(true|false|NULL)\b`, NameBuiltin, nil},
|
||||
{`([a-zA-Z_]\w*)(\s*)(:)(?!:)`, ByGroups(NameLabel, Text, Punctuation), nil},
|
||||
{`[a-zA-Z_]\w*`, Name, nil},
|
||||
},
|
||||
"root": {
|
||||
Include("whitespace"),
|
||||
{`((?:[\w*\s])+?(?:\s|[*]))([a-zA-Z_]\w*)(\s*\([^;]*?\))([^;{]*)(\{)`, ByGroups(UsingSelf("root"), NameFunction, UsingSelf("root"), UsingSelf("root"), Punctuation), Push("function")},
|
||||
{`((?:[\w*\s])+?(?:\s|[*]))([a-zA-Z_]\w*)(\s*\([^;]*?\))([^;]*)(;)`, ByGroups(UsingSelf("root"), NameFunction, UsingSelf("root"), UsingSelf("root"), Punctuation), nil},
|
||||
Default(Push("statement")),
|
||||
{Words(`__`, `\b`, `virtual_inheritance`, `uuidof`, `super`, `single_inheritance`, `multiple_inheritance`, `interface`, `event`), KeywordReserved, nil},
|
||||
{`__(offload|blockingoffload|outer)\b`, KeywordPseudo, nil},
|
||||
},
|
||||
"classname": {
|
||||
{`[a-zA-Z_]\w*`, NameClass, Pop(1)},
|
||||
{`\s*(?=>)`, Text, Pop(1)},
|
||||
},
|
||||
"whitespace": {
|
||||
{`^#if\s+0`, CommentPreproc, Push("if0")},
|
||||
{`^#`, CommentPreproc, Push("macro")},
|
||||
{`^(\s*(?:/[*].*?[*]/\s*)?)(#if\s+0)`, ByGroups(UsingSelf("root"), CommentPreproc), Push("if0")},
|
||||
{`^(\s*(?:/[*].*?[*]/\s*)?)(#)`, ByGroups(UsingSelf("root"), CommentPreproc), Push("macro")},
|
||||
{`\n`, Text, nil},
|
||||
{`\s+`, Text, nil},
|
||||
{`\\\n`, Text, nil},
|
||||
{`//(\n|[\w\W]*?[^\\]\n)`, CommentSingle, nil},
|
||||
{`/(\\\n)?[*][\w\W]*?[*](\\\n)?/`, CommentMultiline, nil},
|
||||
{`/(\\\n)?[*][\w\W]*`, CommentMultiline, nil},
|
||||
},
|
||||
"statement": {
|
||||
Include("whitespace"),
|
||||
Include("statements"),
|
||||
{`[{}]`, Punctuation, nil},
|
||||
{`;`, Punctuation, Pop(1)},
|
||||
},
|
||||
"function": {
|
||||
Include("whitespace"),
|
||||
Include("statements"),
|
||||
{`;`, Punctuation, nil},
|
||||
{`\{`, Punctuation, Push()},
|
||||
{`\}`, Punctuation, Pop(1)},
|
||||
},
|
||||
"string": {
|
||||
{`"`, LiteralString, Pop(1)},
|
||||
{`\\([\\abfnrtv"\']|x[a-fA-F0-9]{2,4}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{8}|[0-7]{1,3})`, LiteralStringEscape, nil},
|
||||
{`[^\\"\n]+`, LiteralString, nil},
|
||||
{`\\\n`, LiteralString, nil},
|
||||
{`\\`, LiteralString, nil},
|
||||
},
|
||||
"macro": {
|
||||
{`(include)(\s*(?:/[*].*?[*]/\s*)?)([^\n]+)`, ByGroups(CommentPreproc, Text, CommentPreprocFile), nil},
|
||||
{`[^/\n]+`, CommentPreproc, nil},
|
||||
{`/[*](.|\n)*?[*]/`, CommentMultiline, nil},
|
||||
{`//.*?\n`, CommentSingle, Pop(1)},
|
||||
{`/`, CommentPreproc, nil},
|
||||
{`(?<=\\)\n`, CommentPreproc, nil},
|
||||
{`\n`, CommentPreproc, Pop(1)},
|
||||
},
|
||||
"if0": {
|
||||
{`^\s*#if.*?(?<!\\)\n`, CommentPreproc, Push()},
|
||||
{`^\s*#el(?:se|if).*\n`, CommentPreproc, Pop(1)},
|
||||
{`^\s*#endif.*?(?<!\\)\n`, CommentPreproc, Pop(1)},
|
||||
{`.*?\n`, Comment, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,48 @@
|
|||
package a
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Awk lexer.
|
||||
var Awk = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Awk",
|
||||
Aliases: []string{"awk", "gawk", "mawk", "nawk"},
|
||||
Filenames: []string{"*.awk"},
|
||||
MimeTypes: []string{"application/x-awk"},
|
||||
},
|
||||
Rules{
|
||||
"commentsandwhitespace": {
|
||||
{`\s+`, Text, nil},
|
||||
{`#.*$`, CommentSingle, nil},
|
||||
},
|
||||
"slashstartsregex": {
|
||||
Include("commentsandwhitespace"),
|
||||
{`/(\\.|[^[/\\\n]|\[(\\.|[^\]\\\n])*])+/\B`, LiteralStringRegex, Pop(1)},
|
||||
{`(?=/)`, Text, Push("#pop", "badregex")},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"badregex": {
|
||||
{`\n`, Text, Pop(1)},
|
||||
},
|
||||
"root": {
|
||||
{`^(?=\s|/)`, Text, Push("slashstartsregex")},
|
||||
Include("commentsandwhitespace"),
|
||||
{`\+\+|--|\|\||&&|in\b|\$|!?~|(\*\*|[-<>+*%\^/!=|])=?`, Operator, Push("slashstartsregex")},
|
||||
{`[{(\[;,]`, Punctuation, Push("slashstartsregex")},
|
||||
{`[})\].]`, Punctuation, nil},
|
||||
{`(break|continue|do|while|exit|for|if|else|return)\b`, Keyword, Push("slashstartsregex")},
|
||||
{`function\b`, KeywordDeclaration, Push("slashstartsregex")},
|
||||
{`(atan2|cos|exp|int|log|rand|sin|sqrt|srand|gensub|gsub|index|length|match|split|sprintf|sub|substr|tolower|toupper|close|fflush|getline|next|nextfile|print|printf|strftime|systime|delete|system)\b`, KeywordReserved, nil},
|
||||
{`(ARGC|ARGIND|ARGV|BEGIN|CONVFMT|ENVIRON|END|ERRNO|FIELDWIDTHS|FILENAME|FNR|FS|IGNORECASE|NF|NR|OFMT|OFS|ORFS|RLENGTH|RS|RSTART|RT|SUBSEP)\b`, NameBuiltin, nil},
|
||||
{`[$a-zA-Z_]\w*`, NameOther, nil},
|
||||
{`[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?`, LiteralNumberFloat, nil},
|
||||
{`0x[0-9a-fA-F]+`, LiteralNumberHex, nil},
|
||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
||||
{`"(\\\\|\\"|[^"])*"`, LiteralStringDouble, nil},
|
||||
{`'(\\\\|\\'|[^'])*'`, LiteralStringSingle, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,46 @@
|
|||
package b
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Ballerina lexer.
|
||||
var Ballerina = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Ballerina",
|
||||
Aliases: []string{"ballerina"},
|
||||
Filenames: []string{"*.bal"},
|
||||
MimeTypes: []string{"text/x-ballerina"},
|
||||
DotAll: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`[^\S\n]+`, Text, nil},
|
||||
{`//.*?\n`, CommentSingle, nil},
|
||||
{`/\*.*?\*/`, CommentMultiline, nil},
|
||||
{`(break|catch|continue|done|else|finally|foreach|forever|fork|if|lock|match|return|throw|transaction|try|while)\b`, Keyword, nil},
|
||||
{`((?:(?:[^\W\d]|\$)[\w.\[\]$<>]*\s+)+?)((?:[^\W\d]|\$)[\w$]*)(\s*)(\()`, ByGroups(UsingSelf("root"), NameFunction, Text, Operator), nil},
|
||||
{`@[^\W\d][\w.]*`, NameDecorator, nil},
|
||||
{`(annotation|bind|but|endpoint|error|function|object|private|public|returns|service|type|var|with|worker)\b`, KeywordDeclaration, nil},
|
||||
{`(boolean|byte|decimal|float|int|json|map|nil|record|string|table|xml)\b`, KeywordType, nil},
|
||||
{`(true|false|null)\b`, KeywordConstant, nil},
|
||||
{`(import)(\s+)`, ByGroups(KeywordNamespace, Text), Push("import")},
|
||||
{`"(\\\\|\\"|[^"])*"`, LiteralString, nil},
|
||||
{`'\\.'|'[^\\]'|'\\u[0-9a-fA-F]{4}'`, LiteralStringChar, nil},
|
||||
{`(\.)((?:[^\W\d]|\$)[\w$]*)`, ByGroups(Operator, NameAttribute), nil},
|
||||
{`^\s*([^\W\d]|\$)[\w$]*:`, NameLabel, nil},
|
||||
{`([^\W\d]|\$)[\w$]*`, Name, nil},
|
||||
{`([0-9][0-9_]*\.([0-9][0-9_]*)?|\.[0-9][0-9_]*)([eE][+\-]?[0-9][0-9_]*)?[fFdD]?|[0-9][eE][+\-]?[0-9][0-9_]*[fFdD]?|[0-9]([eE][+\-]?[0-9][0-9_]*)?[fFdD]|0[xX]([0-9a-fA-F][0-9a-fA-F_]*\.?|([0-9a-fA-F][0-9a-fA-F_]*)?\.[0-9a-fA-F][0-9a-fA-F_]*)[pP][+\-]?[0-9][0-9_]*[fFdD]?`, LiteralNumberFloat, nil},
|
||||
{`0[xX][0-9a-fA-F][0-9a-fA-F_]*[lL]?`, LiteralNumberHex, nil},
|
||||
{`0[bB][01][01_]*[lL]?`, LiteralNumberBin, nil},
|
||||
{`0[0-7_]+[lL]?`, LiteralNumberOct, nil},
|
||||
{`0|[1-9][0-9_]*[lL]?`, LiteralNumberInteger, nil},
|
||||
{`[~^*!%&\[\](){}<>|+=:;,./?-]`, Operator, nil},
|
||||
{`\n`, Text, nil},
|
||||
},
|
||||
"import": {
|
||||
{`[\w.]+`, NameNamespace, Pop(1)},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,95 @@
|
|||
package b
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
var bashAnalyserRe = regexp.MustCompile(`(?m)^#!.*/bin/(?:env |)(?:bash|zsh|sh|ksh)`)
|
||||
|
||||
// Bash lexer.
|
||||
var Bash = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Bash",
|
||||
Aliases: []string{"bash", "sh", "ksh", "zsh", "shell"},
|
||||
Filenames: []string{"*.sh", "*.ksh", "*.bash", "*.ebuild", "*.eclass", "*.exheres-0", "*.exlib", "*.zsh", "*.zshrc", ".bashrc", "bashrc", ".bash_*", "bash_*", "zshrc", ".zshrc", "PKGBUILD"},
|
||||
MimeTypes: []string{"application/x-sh", "application/x-shellscript"},
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
Include("basic"),
|
||||
{"`", LiteralStringBacktick, Push("backticks")},
|
||||
Include("data"),
|
||||
Include("interp"),
|
||||
},
|
||||
"interp": {
|
||||
{`\$\(\(`, Keyword, Push("math")},
|
||||
{`\$\(`, Keyword, Push("paren")},
|
||||
{`\$\{#?`, LiteralStringInterpol, Push("curly")},
|
||||
{`\$[a-zA-Z_]\w*`, NameVariable, nil},
|
||||
{`\$(?:\d+|[#$?!_*@-])`, NameVariable, nil},
|
||||
{`\$`, Text, nil},
|
||||
},
|
||||
"basic": {
|
||||
{`\b(if|fi|else|while|do|done|for|then|return|function|case|select|continue|until|esac|elif)(\s*)\b`, ByGroups(Keyword, Text), nil},
|
||||
{"\\b(alias|bg|bind|break|builtin|caller|cd|command|compgen|complete|declare|dirs|disown|echo|enable|eval|exec|exit|export|false|fc|fg|getopts|hash|help|history|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|set|shift|shopt|source|suspend|test|time|times|trap|true|type|typeset|ulimit|umask|unalias|unset|wait)(?=[\\s)`])", NameBuiltin, nil},
|
||||
{`\A#!.+\n`, CommentPreproc, nil},
|
||||
{`#.*\S`, CommentSingle, nil},
|
||||
{`\\[\w\W]`, LiteralStringEscape, nil},
|
||||
{`(\b\w+)(\s*)(\+?=)`, ByGroups(NameVariable, Text, Operator), nil},
|
||||
{`[\[\]{}()=]`, Operator, nil},
|
||||
{`<<<`, Operator, nil},
|
||||
{`<<-?\s*(\'?)\\?(\w+)[\w\W]+?\2`, LiteralString, nil},
|
||||
{`&&|\|\|`, Operator, nil},
|
||||
},
|
||||
"data": {
|
||||
{`(?s)\$?"(\\\\|\\[0-7]+|\\.|[^"\\$])*"`, LiteralStringDouble, nil},
|
||||
{`"`, LiteralStringDouble, Push("string")},
|
||||
{`(?s)\$'(\\\\|\\[0-7]+|\\.|[^'\\])*'`, LiteralStringSingle, nil},
|
||||
{`(?s)'.*?'`, LiteralStringSingle, nil},
|
||||
{`;`, Punctuation, nil},
|
||||
{`&`, Punctuation, nil},
|
||||
{`\|`, Punctuation, nil},
|
||||
{`\s+`, Text, nil},
|
||||
{`\d+(?= |$)`, LiteralNumber, nil},
|
||||
{"[^=\\s\\[\\]{}()$\"\\'`\\\\<&|;]+", Text, nil},
|
||||
{`<`, Text, nil},
|
||||
},
|
||||
"string": {
|
||||
{`"`, LiteralStringDouble, Pop(1)},
|
||||
{`(?s)(\\\\|\\[0-7]+|\\.|[^"\\$])+`, LiteralStringDouble, nil},
|
||||
Include("interp"),
|
||||
},
|
||||
"curly": {
|
||||
{`\}`, LiteralStringInterpol, Pop(1)},
|
||||
{`:-`, Keyword, nil},
|
||||
{`\w+`, NameVariable, nil},
|
||||
{"[^}:\"\\'`$\\\\]+", Punctuation, nil},
|
||||
{`:`, Punctuation, nil},
|
||||
Include("root"),
|
||||
},
|
||||
"paren": {
|
||||
{`\)`, Keyword, Pop(1)},
|
||||
Include("root"),
|
||||
},
|
||||
"math": {
|
||||
{`\)\)`, Keyword, Pop(1)},
|
||||
{`[-+*/%^|&]|\*\*|\|\|`, Operator, nil},
|
||||
{`\d+#\d+`, LiteralNumber, nil},
|
||||
{`\d+#(?! )`, LiteralNumber, nil},
|
||||
{`\d+`, LiteralNumber, nil},
|
||||
Include("root"),
|
||||
},
|
||||
"backticks": {
|
||||
{"`", LiteralStringBacktick, Pop(1)},
|
||||
Include("root"),
|
||||
},
|
||||
},
|
||||
).SetAnalyser(func(text string) float32 {
|
||||
if bashAnalyserRe.FindString(text) != "" {
|
||||
return 1.0
|
||||
}
|
||||
return 0.0
|
||||
}))
|
|
@ -0,0 +1,194 @@
|
|||
package b
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Batchfile lexer.
|
||||
var Batchfile = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Batchfile",
|
||||
Aliases: []string{"bat", "batch", "dosbatch", "winbatch"},
|
||||
Filenames: []string{"*.bat", "*.cmd"},
|
||||
MimeTypes: []string{"application/x-dos-batch"},
|
||||
CaseInsensitive: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`\)((?=\()|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))(?:(?:[^\n\x1a^]|\^[\n\x1a]?[\w\W])*)`, CommentSingle, nil},
|
||||
{`(?=((?:(?<=^[^:])|^[^:]?)[\t\v\f\r ,;=\xa0]*)(:))`, Text, Push("follow")},
|
||||
{`(?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)`, UsingSelf("text"), nil},
|
||||
Include("redirect"),
|
||||
{`[\n\x1a]+`, Text, nil},
|
||||
{`\(`, Punctuation, Push("root/compound")},
|
||||
{`@+`, Punctuation, nil},
|
||||
{`((?:for|if|rem)(?:(?=(?:\^[\n\x1a]?)?/)|(?:(?!\^)|(?<=m))(?:(?=\()|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?(?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|\t\v\f\r ,;=\xa0])+)?(?:\^[\n\x1a]?)?/(?:\^[\n\x1a]?)?\?)`, ByGroups(Keyword, UsingSelf("text")), Push("follow")},
|
||||
{`(goto(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(]))((?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|[^"%\n\x1a&<>|])*(?:\^[\n\x1a]?)?/(?:\^[\n\x1a]?)?\?(?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|[^"%\n\x1a&<>|])*)`, ByGroups(Keyword, UsingSelf("text")), Push("follow")},
|
||||
{Words(``, `(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(])`, `assoc`, `break`, `cd`, `chdir`, `cls`, `color`, `copy`, `date`, `del`, `dir`, `dpath`, `echo`, `endlocal`, `erase`, `exit`, `ftype`, `keys`, `md`, `mkdir`, `mklink`, `move`, `path`, `pause`, `popd`, `prompt`, `pushd`, `rd`, `ren`, `rename`, `rmdir`, `setlocal`, `shift`, `start`, `time`, `title`, `type`, `ver`, `verify`, `vol`), Keyword, Push("follow")},
|
||||
{`(call)((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?)(:)`, ByGroups(Keyword, UsingSelf("text"), Punctuation), Push("call")},
|
||||
{`call(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(])`, Keyword, nil},
|
||||
{`(for(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])(?!\^))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+))(/f(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))`, ByGroups(Keyword, UsingSelf("text"), Keyword), Push("for/f", "for")},
|
||||
{`(for(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])(?!\^))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+))(/l(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))`, ByGroups(Keyword, UsingSelf("text"), Keyword), Push("for/l", "for")},
|
||||
{`for(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])(?!\^)`, Keyword, Push("for2", "for")},
|
||||
{`(goto(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(]))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?)(:?)`, ByGroups(Keyword, UsingSelf("text"), Punctuation), Push("label")},
|
||||
{`(if(?:(?=\()|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))(?!\^))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?)((?:/i(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))?)((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?)((?:not(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))?)((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?)`, ByGroups(Keyword, UsingSelf("text"), Keyword, UsingSelf("text"), Keyword, UsingSelf("text")), Push("(?", "if")},
|
||||
{`rem(((?=\()|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))(?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?(?:[&<>|]+|(?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|(?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|\t\v\f\r ,;=\xa0])+))+)?.*|(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(])(?:(?:[^\n\x1a^]|\^[\n\x1a]?[\w\W])*))`, CommentSingle, Push("follow")},
|
||||
{`(set(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(]))((?:(?:\^[\n\x1a]?)?[^\S\n])*)(/a)`, ByGroups(Keyword, UsingSelf("text"), Keyword), Push("arithmetic")},
|
||||
{`(set(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(]))((?:(?:\^[\n\x1a]?)?[^\S\n])*)((?:/p)?)((?:(?:\^[\n\x1a]?)?[^\S\n])*)((?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|^=]|\^[\n\x1a]?[^"=])+)?)((?:(?:\^[\n\x1a]?)?=)?)`, ByGroups(Keyword, UsingSelf("text"), Keyword, UsingSelf("text"), UsingSelf("variable"), Punctuation), Push("follow")},
|
||||
Default(Push("follow")),
|
||||
},
|
||||
"follow": {
|
||||
{`((?:(?<=^[^:])|^[^:]?)[\t\v\f\r ,;=\xa0]*)(:)([\t\v\f\r ,;=\xa0]*)((?:(?:[^\n\x1a&<>|\t\v\f\r ,;=\xa0+:^]|\^[\n\x1a]?[\w\W])*))(.*)`, ByGroups(Text, Punctuation, Text, NameLabel, CommentSingle), nil},
|
||||
Include("redirect"),
|
||||
{`(?=[\n\x1a])`, Text, Pop(1)},
|
||||
{`\|\|?|&&?`, Punctuation, Pop(1)},
|
||||
Include("text"),
|
||||
},
|
||||
"arithmetic": {
|
||||
{`0[0-7]+`, LiteralNumberOct, nil},
|
||||
{`0x[\da-f]+`, LiteralNumberHex, nil},
|
||||
{`\d+`, LiteralNumberInteger, nil},
|
||||
{`[(),]+`, Punctuation, nil},
|
||||
{`([=+\-*/!~]|%|\^\^)+`, Operator, nil},
|
||||
{`((?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|(\^[\n\x1a]?)?[^()=+\-*/!~%^"\n\x1a&<>|\t\v\f\r ,;=\xa0]|\^[\n\x1a\t\v\f\r ,;=\xa0]?[\w\W])+`, UsingSelf("variable"), nil},
|
||||
{`(?=[\x00|&])`, Text, Pop(1)},
|
||||
Include("follow"),
|
||||
},
|
||||
"call": {
|
||||
{`(:?)((?:(?:[^\n\x1a&<>|\t\v\f\r ,;=\xa0+:^]|\^[\n\x1a]?[\w\W])*))`, ByGroups(Punctuation, NameLabel), Pop(1)},
|
||||
},
|
||||
"label": {
|
||||
{`((?:(?:[^\n\x1a&<>|\t\v\f\r ,;=\xa0+:^]|\^[\n\x1a]?[\w\W])*)?)((?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|\^[\n\x1a]?[\w\W]|[^"%^\n\x1a&<>|])*)`, ByGroups(NameLabel, CommentSingle), Pop(1)},
|
||||
},
|
||||
"redirect": {
|
||||
{`((?:(?<=[\n\x1a\t\v\f\r ,;=\xa0])\d)?)(>>?&|<&)([\n\x1a\t\v\f\r ,;=\xa0]*)(\d)`, ByGroups(LiteralNumberInteger, Punctuation, Text, LiteralNumberInteger), nil},
|
||||
{`((?:(?<=[\n\x1a\t\v\f\r ,;=\xa0])(?<!\^[\n\x1a])\d)?)(>>?|<)((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?(?:[&<>|]+|(?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|(?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|\t\v\f\r ,;=\xa0])+))+))`, ByGroups(LiteralNumberInteger, Punctuation, UsingSelf("text")), nil},
|
||||
},
|
||||
"root/compound": {
|
||||
{`\)`, Punctuation, Pop(1)},
|
||||
{`(?=((?:(?<=^[^:])|^[^:]?)[\t\v\f\r ,;=\xa0]*)(:))`, Text, Push("follow/compound")},
|
||||
{`(?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)`, UsingSelf("text"), nil},
|
||||
Include("redirect/compound"),
|
||||
{`[\n\x1a]+`, Text, nil},
|
||||
{`\(`, Punctuation, Push("root/compound")},
|
||||
{`@+`, Punctuation, nil},
|
||||
{`((?:for|if|rem)(?:(?=(?:\^[\n\x1a]?)?/)|(?:(?!\^)|(?<=m))(?:(?=\()|(?:(?=\))|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])))))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?(?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|\t\v\f\r ,;=\xa0)])+)?(?:\^[\n\x1a]?)?/(?:\^[\n\x1a]?)?\?)`, ByGroups(Keyword, UsingSelf("text")), Push("follow/compound")},
|
||||
{`(goto(?:(?=\))|(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(])))((?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|[^"%\n\x1a&<>|)])*(?:\^[\n\x1a]?)?/(?:\^[\n\x1a]?)?\?(?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|[^"%\n\x1a&<>|)])*)`, ByGroups(Keyword, UsingSelf("text")), Push("follow/compound")},
|
||||
{Words(``, `(?:(?=\))|(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(]))`, `assoc`, `break`, `cd`, `chdir`, `cls`, `color`, `copy`, `date`, `del`, `dir`, `dpath`, `echo`, `endlocal`, `erase`, `exit`, `ftype`, `keys`, `md`, `mkdir`, `mklink`, `move`, `path`, `pause`, `popd`, `prompt`, `pushd`, `rd`, `ren`, `rename`, `rmdir`, `setlocal`, `shift`, `start`, `time`, `title`, `type`, `ver`, `verify`, `vol`), Keyword, Push("follow/compound")},
|
||||
{`(call)((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?)(:)`, ByGroups(Keyword, UsingSelf("text"), Punctuation), Push("call/compound")},
|
||||
{`call(?:(?=\))|(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(]))`, Keyword, nil},
|
||||
{`(for(?:(?=\))|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))(?!\^))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+))(/f(?:(?=\))|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])))`, ByGroups(Keyword, UsingSelf("text"), Keyword), Push("for/f", "for")},
|
||||
{`(for(?:(?=\))|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))(?!\^))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+))(/l(?:(?=\))|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])))`, ByGroups(Keyword, UsingSelf("text"), Keyword), Push("for/l", "for")},
|
||||
{`for(?:(?=\))|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))(?!\^)`, Keyword, Push("for2", "for")},
|
||||
{`(goto(?:(?=\))|(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(])))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?)(:?)`, ByGroups(Keyword, UsingSelf("text"), Punctuation), Push("label/compound")},
|
||||
{`(if(?:(?=\()|(?:(?=\))|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])))(?!\^))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?)((?:/i(?:(?=\))|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])))?)((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?)((?:not(?:(?=\))|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])))?)((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?)`, ByGroups(Keyword, UsingSelf("text"), Keyword, UsingSelf("text"), Keyword, UsingSelf("text")), Push("(?", "if")},
|
||||
{`rem(((?=\()|(?:(?=\))|(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])))(?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?(?:[&<>|]+|(?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|(?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|\t\v\f\r ,;=\xa0])+))+)?.*|(?:(?=\))|(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(]))(?:(?:[^\n\x1a^)]|\^[\n\x1a]?[^)])*))`, CommentSingle, Push("follow/compound")},
|
||||
{`(set(?:(?=\))|(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(])))((?:(?:\^[\n\x1a]?)?[^\S\n])*)(/a)`, ByGroups(Keyword, UsingSelf("text"), Keyword), Push("arithmetic/compound")},
|
||||
{`(set(?:(?=\))|(?=(?:\^[\n\x1a]?)?[\t\v\f\r ,;=\xa0+./:[\\\]]|[\n\x1a&<>|(])))((?:(?:\^[\n\x1a]?)?[^\S\n])*)((?:/p)?)((?:(?:\^[\n\x1a]?)?[^\S\n])*)((?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|^=)]|\^[\n\x1a]?[^"=])+)?)((?:(?:\^[\n\x1a]?)?=)?)`, ByGroups(Keyword, UsingSelf("text"), Keyword, UsingSelf("text"), UsingSelf("variable"), Punctuation), Push("follow/compound")},
|
||||
Default(Push("follow/compound")),
|
||||
},
|
||||
"follow/compound": {
|
||||
{`(?=\))`, Text, Pop(1)},
|
||||
{`((?:(?<=^[^:])|^[^:]?)[\t\v\f\r ,;=\xa0]*)(:)([\t\v\f\r ,;=\xa0]*)((?:(?:[^\n\x1a&<>|\t\v\f\r ,;=\xa0+:^)]|\^[\n\x1a]?[^)])*))(.*)`, ByGroups(Text, Punctuation, Text, NameLabel, CommentSingle), nil},
|
||||
Include("redirect/compound"),
|
||||
{`(?=[\n\x1a])`, Text, Pop(1)},
|
||||
{`\|\|?|&&?`, Punctuation, Pop(1)},
|
||||
Include("text"),
|
||||
},
|
||||
"arithmetic/compound": {
|
||||
{`(?=\))`, Text, Pop(1)},
|
||||
{`0[0-7]+`, LiteralNumberOct, nil},
|
||||
{`0x[\da-f]+`, LiteralNumberHex, nil},
|
||||
{`\d+`, LiteralNumberInteger, nil},
|
||||
{`[(),]+`, Punctuation, nil},
|
||||
{`([=+\-*/!~]|%|\^\^)+`, Operator, nil},
|
||||
{`((?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|(\^[\n\x1a]?)?[^()=+\-*/!~%^"\n\x1a&<>|\t\v\f\r ,;=\xa0]|\^[\n\x1a\t\v\f\r ,;=\xa0]?[^)])+`, UsingSelf("variable"), nil},
|
||||
{`(?=[\x00|&])`, Text, Pop(1)},
|
||||
Include("follow"),
|
||||
},
|
||||
"call/compound": {
|
||||
{`(?=\))`, Text, Pop(1)},
|
||||
{`(:?)((?:(?:[^\n\x1a&<>|\t\v\f\r ,;=\xa0+:^)]|\^[\n\x1a]?[^)])*))`, ByGroups(Punctuation, NameLabel), Pop(1)},
|
||||
},
|
||||
"label/compound": {
|
||||
{`(?=\))`, Text, Pop(1)},
|
||||
{`((?:(?:[^\n\x1a&<>|\t\v\f\r ,;=\xa0+:^)]|\^[\n\x1a]?[^)])*)?)((?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|\^[\n\x1a]?[^)]|[^"%^\n\x1a&<>|)])*)`, ByGroups(NameLabel, CommentSingle), Pop(1)},
|
||||
},
|
||||
"redirect/compound": {
|
||||
{`((?:(?<=[\n\x1a\t\v\f\r ,;=\xa0])\d)?)(>>?&|<&)([\n\x1a\t\v\f\r ,;=\xa0]*)(\d)`, ByGroups(LiteralNumberInteger, Punctuation, Text, LiteralNumberInteger), nil},
|
||||
{`((?:(?<=[\n\x1a\t\v\f\r ,;=\xa0])(?<!\^[\n\x1a])\d)?)(>>?|<)((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?(?:[&<>|]+|(?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|(?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|\t\v\f\r ,;=\xa0)])+))+))`, ByGroups(LiteralNumberInteger, Punctuation, UsingSelf("text")), nil},
|
||||
},
|
||||
"variable-or-escape": {
|
||||
{`(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))`, NameVariable, nil},
|
||||
{`%%|\^[\n\x1a]?(\^!|[\w\W])`, LiteralStringEscape, nil},
|
||||
},
|
||||
"string": {
|
||||
{`"`, LiteralStringDouble, Pop(1)},
|
||||
{`(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))`, NameVariable, nil},
|
||||
{`\^!|%%`, LiteralStringEscape, nil},
|
||||
{`[^"%^\n\x1a]+|[%^]`, LiteralStringDouble, nil},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"sqstring": {
|
||||
Include("variable-or-escape"),
|
||||
{`[^%]+|%`, LiteralStringSingle, nil},
|
||||
},
|
||||
"bqstring": {
|
||||
Include("variable-or-escape"),
|
||||
{`[^%]+|%`, LiteralStringBacktick, nil},
|
||||
},
|
||||
"text": {
|
||||
{`"`, LiteralStringDouble, Push("string")},
|
||||
Include("variable-or-escape"),
|
||||
{`[^"%^\n\x1a&<>|\t\v\f\r ,;=\xa0\d)]+|.`, Text, nil},
|
||||
},
|
||||
"variable": {
|
||||
{`"`, LiteralStringDouble, Push("string")},
|
||||
Include("variable-or-escape"),
|
||||
{`[^"%^\n\x1a]+|.`, NameVariable, nil},
|
||||
},
|
||||
"for": {
|
||||
{`((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+))(in)((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+))(\()`, ByGroups(UsingSelf("text"), Keyword, UsingSelf("text"), Punctuation), Pop(1)},
|
||||
Include("follow"),
|
||||
},
|
||||
"for2": {
|
||||
{`\)`, Punctuation, nil},
|
||||
{`((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+))(do(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))`, ByGroups(UsingSelf("text"), Keyword), Pop(1)},
|
||||
{`[\n\x1a]+`, Text, nil},
|
||||
Include("follow"),
|
||||
},
|
||||
"for/f": {
|
||||
{`(")((?:(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|[^"])*?")([\n\x1a\t\v\f\r ,;=\xa0]*)(\))`, ByGroups(LiteralStringDouble, UsingSelf("string"), Text, Punctuation), nil},
|
||||
{`"`, LiteralStringDouble, Push("#pop", "for2", "string")},
|
||||
{`('(?:%%|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|[\w\W])*?')([\n\x1a\t\v\f\r ,;=\xa0]*)(\))`, ByGroups(UsingSelf("sqstring"), Text, Punctuation), nil},
|
||||
{"(`(?:%%|(?:(?:%(?:\\*|(?:~[a-z]*(?:\\$[^:]+:)?)?\\d|[^%:\\n\\x1a]+(?::(?:~(?:-?\\d+)?(?:,(?:-?\\d+)?)?|(?:[^%\\n\\x1a^]|\\^[^%\\n\\x1a])[^=\\n\\x1a]*=(?:[^%\\n\\x1a^]|\\^[^%\\n\\x1a])*)?)?%))|(?:\\^?![^!:\\n\\x1a]+(?::(?:~(?:-?\\d+)?(?:,(?:-?\\d+)?)?|(?:[^!\\n\\x1a^]|\\^[^!\\n\\x1a])[^=\\n\\x1a]*=(?:[^!\\n\\x1a^]|\\^[^!\\n\\x1a])*)?)?\\^?!))|[\\w\\W])*?`)([\\n\\x1a\\t\\v\\f\\r ,;=\\xa0]*)(\\))", ByGroups(UsingSelf("bqstring"), Text, Punctuation), nil},
|
||||
Include("for2"),
|
||||
},
|
||||
"for/l": {
|
||||
{`-?\d+`, LiteralNumberInteger, nil},
|
||||
Include("for2"),
|
||||
},
|
||||
"if": {
|
||||
{`((?:cmdextversion|errorlevel)(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+))(\d+)`, ByGroups(Keyword, UsingSelf("text"), LiteralNumberInteger), Pop(1)},
|
||||
{`(defined(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+))((?:[&<>|]+|(?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|(?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|\t\v\f\r ,;=\xa0])+))+))`, ByGroups(Keyword, UsingSelf("text"), UsingSelf("variable")), Pop(1)},
|
||||
{`(exist(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)(?:[&<>|]+|(?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|(?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|\t\v\f\r ,;=\xa0])+))+))`, ByGroups(Keyword, UsingSelf("text")), Pop(1)},
|
||||
{`((?:-?(?:0[0-7]+|0x[\da-f]+|\d+)(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a]))(?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+))((?:equ|geq|gtr|leq|lss|neq))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)(?:-?(?:0[0-7]+|0x[\da-f]+|\d+)(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])))`, ByGroups(UsingSelf("arithmetic"), OperatorWord, UsingSelf("arithmetic")), Pop(1)},
|
||||
{`(?:[&<>|]+|(?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|(?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|\t\v\f\r ,;=\xa0])+))+)`, UsingSelf("text"), Push("#pop", "if2")},
|
||||
},
|
||||
"if2": {
|
||||
{`((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?)(==)((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)?(?:[&<>|]+|(?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|(?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|\t\v\f\r ,;=\xa0])+))+))`, ByGroups(UsingSelf("text"), Operator, UsingSelf("text")), Pop(1)},
|
||||
{`((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+))((?:equ|geq|gtr|leq|lss|neq))((?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)(?:[&<>|]+|(?:(?:"[^\n\x1a"]*(?:"|(?=[\n\x1a])))|(?:(?:%(?:\*|(?:~[a-z]*(?:\$[^:]+:)?)?\d|[^%:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^%\n\x1a^]|\^[^%\n\x1a])[^=\n\x1a]*=(?:[^%\n\x1a^]|\^[^%\n\x1a])*)?)?%))|(?:\^?![^!:\n\x1a]+(?::(?:~(?:-?\d+)?(?:,(?:-?\d+)?)?|(?:[^!\n\x1a^]|\^[^!\n\x1a])[^=\n\x1a]*=(?:[^!\n\x1a^]|\^[^!\n\x1a])*)?)?\^?!))|(?:(?:(?:\^[\n\x1a]?)?[^"\n\x1a&<>|\t\v\f\r ,;=\xa0])+))+))`, ByGroups(UsingSelf("text"), OperatorWord, UsingSelf("text")), Pop(1)},
|
||||
},
|
||||
"(?": {
|
||||
{`(?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)`, UsingSelf("text"), nil},
|
||||
{`\(`, Punctuation, Push("#pop", "else?", "root/compound")},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"else?": {
|
||||
{`(?:(?:(?:\^[\n\x1a])?[\t\v\f\r ,;=\xa0])+)`, UsingSelf("text"), nil},
|
||||
{`else(?=\^?[\t\v\f\r ,;=\xa0]|[&<>|\n\x1a])`, Keyword, Pop(1)},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,76 @@
|
|||
package b
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Bibtex lexer.
|
||||
var Bibtex = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "BibTeX",
|
||||
Aliases: []string{"bib", "bibtex"},
|
||||
Filenames: []string{"*.bib"},
|
||||
MimeTypes: []string{"text/x-bibtex"},
|
||||
NotMultiline: true,
|
||||
CaseInsensitive: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
Include("whitespace"),
|
||||
{`@comment`, Comment, nil},
|
||||
{`@preamble`, NameClass, Push("closing-brace", "value", "opening-brace")},
|
||||
{`@string`, NameClass, Push("closing-brace", "field", "opening-brace")},
|
||||
{"@[a-z_@!$&*+\\-./:;<>?\\[\\\\\\]^`|~][\\w@!$&*+\\-./:;<>?\\[\\\\\\]^`|~]*", NameClass, Push("closing-brace", "command-body", "opening-brace")},
|
||||
{`.+`, Comment, nil},
|
||||
},
|
||||
"opening-brace": {
|
||||
Include("whitespace"),
|
||||
{`[{(]`, Punctuation, Pop(1)},
|
||||
},
|
||||
"closing-brace": {
|
||||
Include("whitespace"),
|
||||
{`[})]`, Punctuation, Pop(1)},
|
||||
},
|
||||
"command-body": {
|
||||
Include("whitespace"),
|
||||
{`[^\s\,\}]+`, NameLabel, Push("#pop", "fields")},
|
||||
},
|
||||
"fields": {
|
||||
Include("whitespace"),
|
||||
{`,`, Punctuation, Push("field")},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"field": {
|
||||
Include("whitespace"),
|
||||
{"[a-z_@!$&*+\\-./:;<>?\\[\\\\\\]^`|~][\\w@!$&*+\\-./:;<>?\\[\\\\\\]^`|~]*", NameAttribute, Push("value", "=")},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"=": {
|
||||
Include("whitespace"),
|
||||
{`=`, Punctuation, Pop(1)},
|
||||
},
|
||||
"value": {
|
||||
Include("whitespace"),
|
||||
{"[a-z_@!$&*+\\-./:;<>?\\[\\\\\\]^`|~][\\w@!$&*+\\-./:;<>?\\[\\\\\\]^`|~]*", NameVariable, nil},
|
||||
{`"`, LiteralString, Push("quoted-string")},
|
||||
{`\{`, LiteralString, Push("braced-string")},
|
||||
{`[\d]+`, LiteralNumber, nil},
|
||||
{`#`, Punctuation, nil},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"quoted-string": {
|
||||
{`\{`, LiteralString, Push("braced-string")},
|
||||
{`"`, LiteralString, Pop(1)},
|
||||
{`[^\{\"]+`, LiteralString, nil},
|
||||
},
|
||||
"braced-string": {
|
||||
{`\{`, LiteralString, Push()},
|
||||
{`\}`, LiteralString, Pop(1)},
|
||||
{`[^\{\}]+`, LiteralString, nil},
|
||||
},
|
||||
"whitespace": {
|
||||
{`\s+`, Text, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,48 @@
|
|||
package b
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Blitzbasic lexer.
|
||||
var Blitzbasic = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "BlitzBasic",
|
||||
Aliases: []string{"blitzbasic", "b3d", "bplus"},
|
||||
Filenames: []string{"*.bb", "*.decls"},
|
||||
MimeTypes: []string{"text/x-bb"},
|
||||
CaseInsensitive: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`[ \t]+`, Text, nil},
|
||||
{`;.*?\n`, CommentSingle, nil},
|
||||
{`"`, LiteralStringDouble, Push("string")},
|
||||
{`[0-9]+\.[0-9]*(?!\.)`, LiteralNumberFloat, nil},
|
||||
{`\.[0-9]+(?!\.)`, LiteralNumberFloat, nil},
|
||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
||||
{`\$[0-9a-f]+`, LiteralNumberHex, nil},
|
||||
{`\%[10]+`, LiteralNumberBin, nil},
|
||||
{Words(`\b`, `\b`, `Shl`, `Shr`, `Sar`, `Mod`, `Or`, `And`, `Not`, `Abs`, `Sgn`, `Handle`, `Int`, `Float`, `Str`, `First`, `Last`, `Before`, `After`), Operator, nil},
|
||||
{`([+\-*/~=<>^])`, Operator, nil},
|
||||
{`[(),:\[\]\\]`, Punctuation, nil},
|
||||
{`\.([ \t]*)([a-z]\w*)`, NameLabel, nil},
|
||||
{`\b(New)\b([ \t]+)([a-z]\w*)`, ByGroups(KeywordReserved, Text, NameClass), nil},
|
||||
{`\b(Gosub|Goto)\b([ \t]+)([a-z]\w*)`, ByGroups(KeywordReserved, Text, NameLabel), nil},
|
||||
{`\b(Object)\b([ \t]*)([.])([ \t]*)([a-z]\w*)\b`, ByGroups(Operator, Text, Punctuation, Text, NameClass), nil},
|
||||
{`\b([a-z]\w*)(?:([ \t]*)(@{1,2}|[#$%])|([ \t]*)([.])([ \t]*)(?:([a-z]\w*)))?\b([ \t]*)(\()`, ByGroups(NameFunction, Text, KeywordType, Text, Punctuation, Text, NameClass, Text, Punctuation), nil},
|
||||
{`\b(Function)\b([ \t]+)([a-z]\w*)(?:([ \t]*)(@{1,2}|[#$%])|([ \t]*)([.])([ \t]*)(?:([a-z]\w*)))?`, ByGroups(KeywordReserved, Text, NameFunction, Text, KeywordType, Text, Punctuation, Text, NameClass), nil},
|
||||
{`\b(Type)([ \t]+)([a-z]\w*)`, ByGroups(KeywordReserved, Text, NameClass), nil},
|
||||
{`\b(Pi|True|False|Null)\b`, KeywordConstant, nil},
|
||||
{`\b(Local|Global|Const|Field|Dim)\b`, KeywordDeclaration, nil},
|
||||
{Words(`\b`, `\b`, `End`, `Return`, `Exit`, `Chr`, `Len`, `Asc`, `New`, `Delete`, `Insert`, `Include`, `Function`, `Type`, `If`, `Then`, `Else`, `ElseIf`, `EndIf`, `For`, `To`, `Next`, `Step`, `Each`, `While`, `Wend`, `Repeat`, `Until`, `Forever`, `Select`, `Case`, `Default`, `Goto`, `Gosub`, `Data`, `Read`, `Restore`), KeywordReserved, nil},
|
||||
{`([a-z]\w*)(?:([ \t]*)(@{1,2}|[#$%])|([ \t]*)([.])([ \t]*)(?:([a-z]\w*)))?`, ByGroups(NameVariable, Text, KeywordType, Text, Punctuation, Text, NameClass), nil},
|
||||
},
|
||||
"string": {
|
||||
{`""`, LiteralStringDouble, nil},
|
||||
{`"C?`, LiteralStringDouble, Pop(1)},
|
||||
{`[^"]+`, LiteralStringDouble, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,24 @@
|
|||
package b
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Bnf lexer.
|
||||
var Bnf = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "BNF",
|
||||
Aliases: []string{"bnf"},
|
||||
Filenames: []string{"*.bnf"},
|
||||
MimeTypes: []string{"text/x-bnf"},
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`(<)([ -;=?-~]+)(>)`, ByGroups(Punctuation, NameClass, Punctuation), nil},
|
||||
{`::=`, Operator, nil},
|
||||
{`[^<>:]+`, Text, nil},
|
||||
{`.`, Text, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,34 @@
|
|||
package b
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Brainfuck lexer.
|
||||
var Brainfuck = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Brainfuck",
|
||||
Aliases: []string{"brainfuck", "bf"},
|
||||
Filenames: []string{"*.bf", "*.b"},
|
||||
MimeTypes: []string{"application/x-brainfuck"},
|
||||
},
|
||||
Rules{
|
||||
"common": {
|
||||
{`[.,]+`, NameTag, nil},
|
||||
{`[+-]+`, NameBuiltin, nil},
|
||||
{`[<>]+`, NameVariable, nil},
|
||||
{`[^.,+\-<>\[\]]+`, Comment, nil},
|
||||
},
|
||||
"root": {
|
||||
{`\[`, Keyword, Push("loop")},
|
||||
{`\]`, Error, nil},
|
||||
Include("common"),
|
||||
},
|
||||
"loop": {
|
||||
{`\[`, Keyword, Push()},
|
||||
{`\]`, Keyword, Pop(1)},
|
||||
Include("common"),
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,91 @@
|
|||
package c
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// C lexer.
|
||||
var C = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "C",
|
||||
Aliases: []string{"c"},
|
||||
Filenames: []string{"*.c", "*.h", "*.idc"},
|
||||
MimeTypes: []string{"text/x-chdr", "text/x-csrc"},
|
||||
},
|
||||
Rules{
|
||||
"whitespace": {
|
||||
{`^#if\s+0`, CommentPreproc, Push("if0")},
|
||||
{`^#`, CommentPreproc, Push("macro")},
|
||||
{`^(\s*(?:/[*].*?[*]/\s*)?)(#if\s+0)`, ByGroups(UsingSelf("root"), CommentPreproc), Push("if0")},
|
||||
{`^(\s*(?:/[*].*?[*]/\s*)?)(#)`, ByGroups(UsingSelf("root"), CommentPreproc), Push("macro")},
|
||||
{`\n`, Text, nil},
|
||||
{`\s+`, Text, nil},
|
||||
{`\\\n`, Text, nil},
|
||||
{`//(\n|[\w\W]*?[^\\]\n)`, CommentSingle, nil},
|
||||
{`/(\\\n)?[*][\w\W]*?[*](\\\n)?/`, CommentMultiline, nil},
|
||||
{`/(\\\n)?[*][\w\W]*`, CommentMultiline, nil},
|
||||
},
|
||||
"statements": {
|
||||
{`(L?)(")`, ByGroups(LiteralStringAffix, LiteralString), Push("string")},
|
||||
{`(L?)(')(\\.|\\[0-7]{1,3}|\\x[a-fA-F0-9]{1,2}|[^\\\'\n])(')`, ByGroups(LiteralStringAffix, LiteralStringChar, LiteralStringChar, LiteralStringChar), nil},
|
||||
{`(\d+\.\d*|\.\d+|\d+)[eE][+-]?\d+[LlUu]*`, LiteralNumberFloat, nil},
|
||||
{`(\d+\.\d*|\.\d+|\d+[fF])[fF]?`, LiteralNumberFloat, nil},
|
||||
{`0x[0-9a-fA-F]+[LlUu]*`, LiteralNumberHex, nil},
|
||||
{`0[0-7]+[LlUu]*`, LiteralNumberOct, nil},
|
||||
{`\d+[LlUu]*`, LiteralNumberInteger, nil},
|
||||
{`\*/`, Error, nil},
|
||||
{`[~!%^&*+=|?:<>/-]`, Operator, nil},
|
||||
{`[()\[\],.]`, Punctuation, nil},
|
||||
{Words(``, `\b`, `asm`, `auto`, `break`, `case`, `const`, `continue`, `default`, `do`, `else`, `enum`, `extern`, `for`, `goto`, `if`, `register`, `restricted`, `return`, `sizeof`, `static`, `struct`, `switch`, `typedef`, `union`, `volatile`, `while`), Keyword, nil},
|
||||
{`(bool|int|long|float|short|double|char|unsigned|signed|void)\b`, KeywordType, nil},
|
||||
{Words(``, `\b`, `inline`, `_inline`, `__inline`, `naked`, `restrict`, `thread`, `typename`), KeywordReserved, nil},
|
||||
{`(__m(128i|128d|128|64))\b`, KeywordReserved, nil},
|
||||
{Words(`__`, `\b`, `asm`, `int8`, `based`, `except`, `int16`, `stdcall`, `cdecl`, `fastcall`, `int32`, `declspec`, `finally`, `int64`, `try`, `leave`, `wchar_t`, `w64`, `unaligned`, `raise`, `noop`, `identifier`, `forceinline`, `assume`), KeywordReserved, nil},
|
||||
{`(true|false|NULL)\b`, NameBuiltin, nil},
|
||||
{`([a-zA-Z_]\w*)(\s*)(:)(?!:)`, ByGroups(NameLabel, Text, Punctuation), nil},
|
||||
{`[a-zA-Z_]\w*`, Name, nil},
|
||||
},
|
||||
"root": {
|
||||
Include("whitespace"),
|
||||
{`((?:[\w*\s])+?(?:\s|[*]))([a-zA-Z_]\w*)(\s*\([^;]*?\))([^;{]*)(\{)`, ByGroups(UsingSelf("root"), NameFunction, UsingSelf("root"), UsingSelf("root"), Punctuation), Push("function")},
|
||||
{`((?:[\w*\s])+?(?:\s|[*]))([a-zA-Z_]\w*)(\s*\([^;]*?\))([^;]*)(;)`, ByGroups(UsingSelf("root"), NameFunction, UsingSelf("root"), UsingSelf("root"), Punctuation), nil},
|
||||
Default(Push("statement")),
|
||||
},
|
||||
"statement": {
|
||||
Include("whitespace"),
|
||||
Include("statements"),
|
||||
{`[{}]`, Punctuation, nil},
|
||||
{`;`, Punctuation, Pop(1)},
|
||||
},
|
||||
"function": {
|
||||
Include("whitespace"),
|
||||
Include("statements"),
|
||||
{`;`, Punctuation, nil},
|
||||
{`\{`, Punctuation, Push()},
|
||||
{`\}`, Punctuation, Pop(1)},
|
||||
},
|
||||
"string": {
|
||||
{`"`, LiteralString, Pop(1)},
|
||||
{`\\([\\abfnrtv"\']|x[a-fA-F0-9]{2,4}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{8}|[0-7]{1,3})`, LiteralStringEscape, nil},
|
||||
{`[^\\"\n]+`, LiteralString, nil},
|
||||
{`\\\n`, LiteralString, nil},
|
||||
{`\\`, LiteralString, nil},
|
||||
},
|
||||
"macro": {
|
||||
{`(include)(\s*(?:/[*].*?[*]/\s*)?)([^\n]+)`, ByGroups(CommentPreproc, Text, CommentPreprocFile), nil},
|
||||
{`[^/\n]+`, CommentPreproc, nil},
|
||||
{`/[*](.|\n)*?[*]/`, CommentMultiline, nil},
|
||||
{`//.*?\n`, CommentSingle, Pop(1)},
|
||||
{`/`, CommentPreproc, nil},
|
||||
{`(?<=\\)\n`, CommentPreproc, nil},
|
||||
{`\n`, CommentPreproc, Pop(1)},
|
||||
},
|
||||
"if0": {
|
||||
{`^\s*#if.*?(?<!\\)\n`, CommentPreproc, Push()},
|
||||
{`^\s*#el(?:se|if).*\n`, CommentPreproc, Pop(1)},
|
||||
{`^\s*#endif.*?(?<!\\)\n`, CommentPreproc, Pop(1)},
|
||||
{`.*?\n`, Comment, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,61 @@
|
|||
package c
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Cap'N'Proto Proto lexer.
|
||||
var CapNProto = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Cap'n Proto",
|
||||
Aliases: []string{"capnp"},
|
||||
Filenames: []string{"*.capnp"},
|
||||
MimeTypes: []string{},
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`#.*?$`, CommentSingle, nil},
|
||||
{`@[0-9a-zA-Z]*`, NameDecorator, nil},
|
||||
{`=`, Literal, Push("expression")},
|
||||
{`:`, NameClass, Push("type")},
|
||||
{`\$`, NameAttribute, Push("annotation")},
|
||||
{`(struct|enum|interface|union|import|using|const|annotation|extends|in|of|on|as|with|from|fixed)\b`, Keyword, nil},
|
||||
{`[\w.]+`, Name, nil},
|
||||
{`[^#@=:$\w]+`, Text, nil},
|
||||
},
|
||||
"type": {
|
||||
{`[^][=;,(){}$]+`, NameClass, nil},
|
||||
{`[[(]`, NameClass, Push("parentype")},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"parentype": {
|
||||
{`[^][;()]+`, NameClass, nil},
|
||||
{`[[(]`, NameClass, Push()},
|
||||
{`[])]`, NameClass, Pop(1)},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"expression": {
|
||||
{`[^][;,(){}$]+`, Literal, nil},
|
||||
{`[[(]`, Literal, Push("parenexp")},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"parenexp": {
|
||||
{`[^][;()]+`, Literal, nil},
|
||||
{`[[(]`, Literal, Push()},
|
||||
{`[])]`, Literal, Pop(1)},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"annotation": {
|
||||
{`[^][;,(){}=:]+`, NameAttribute, nil},
|
||||
{`[[(]`, NameAttribute, Push("annexp")},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"annexp": {
|
||||
{`[^][;()]+`, NameAttribute, nil},
|
||||
{`[[(]`, NameAttribute, Push()},
|
||||
{`[])]`, NameAttribute, Pop(1)},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,63 @@
|
|||
package c
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Ceylon lexer.
|
||||
var Ceylon = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Ceylon",
|
||||
Aliases: []string{"ceylon"},
|
||||
Filenames: []string{"*.ceylon"},
|
||||
MimeTypes: []string{"text/x-ceylon"},
|
||||
DotAll: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`^(\s*(?:[a-zA-Z_][\w.\[\]]*\s+)+?)([a-zA-Z_]\w*)(\s*)(\()`, ByGroups(UsingSelf("root"), NameFunction, Text, Operator), nil},
|
||||
{`[^\S\n]+`, Text, nil},
|
||||
{`//.*?\n`, CommentSingle, nil},
|
||||
{`/\*`, CommentMultiline, Push("comment")},
|
||||
{`(shared|abstract|formal|default|actual|variable|deprecated|small|late|literal|doc|by|see|throws|optional|license|tagged|final|native|annotation|sealed)\b`, NameDecorator, nil},
|
||||
{`(break|case|catch|continue|else|finally|for|in|if|return|switch|this|throw|try|while|is|exists|dynamic|nonempty|then|outer|assert|let)\b`, Keyword, nil},
|
||||
{`(abstracts|extends|satisfies|super|given|of|out|assign)\b`, KeywordDeclaration, nil},
|
||||
{`(function|value|void|new)\b`, KeywordType, nil},
|
||||
{`(assembly|module|package)(\s+)`, ByGroups(KeywordNamespace, Text), nil},
|
||||
{`(true|false|null)\b`, KeywordConstant, nil},
|
||||
{`(class|interface|object|alias)(\s+)`, ByGroups(KeywordDeclaration, Text), Push("class")},
|
||||
{`(import)(\s+)`, ByGroups(KeywordNamespace, Text), Push("import")},
|
||||
{`"(\\\\|\\"|[^"])*"`, LiteralString, nil},
|
||||
{`'\\.'|'[^\\]'|'\\\{#[0-9a-fA-F]{4}\}'`, LiteralStringChar, nil},
|
||||
{"\".*``.*``.*\"", LiteralStringInterpol, nil},
|
||||
{`(\.)([a-z_]\w*)`, ByGroups(Operator, NameAttribute), nil},
|
||||
{`[a-zA-Z_]\w*:`, NameLabel, nil},
|
||||
{`[a-zA-Z_]\w*`, Name, nil},
|
||||
{`[~^*!%&\[\](){}<>|+=:;,./?-]`, Operator, nil},
|
||||
{`\d{1,3}(_\d{3})+\.\d{1,3}(_\d{3})+[kMGTPmunpf]?`, LiteralNumberFloat, nil},
|
||||
{`\d{1,3}(_\d{3})+\.[0-9]+([eE][+-]?[0-9]+)?[kMGTPmunpf]?`, LiteralNumberFloat, nil},
|
||||
{`[0-9][0-9]*\.\d{1,3}(_\d{3})+[kMGTPmunpf]?`, LiteralNumberFloat, nil},
|
||||
{`[0-9][0-9]*\.[0-9]+([eE][+-]?[0-9]+)?[kMGTPmunpf]?`, LiteralNumberFloat, nil},
|
||||
{`#([0-9a-fA-F]{4})(_[0-9a-fA-F]{4})+`, LiteralNumberHex, nil},
|
||||
{`#[0-9a-fA-F]+`, LiteralNumberHex, nil},
|
||||
{`\$([01]{4})(_[01]{4})+`, LiteralNumberBin, nil},
|
||||
{`\$[01]+`, LiteralNumberBin, nil},
|
||||
{`\d{1,3}(_\d{3})+[kMGTP]?`, LiteralNumberInteger, nil},
|
||||
{`[0-9]+[kMGTP]?`, LiteralNumberInteger, nil},
|
||||
{`\n`, Text, nil},
|
||||
},
|
||||
"class": {
|
||||
{`[A-Za-z_]\w*`, NameClass, Pop(1)},
|
||||
},
|
||||
"import": {
|
||||
{`[a-z][\w.]*`, NameNamespace, Pop(1)},
|
||||
},
|
||||
"comment": {
|
||||
{`[^*/]`, CommentMultiline, nil},
|
||||
{`/\*`, CommentMultiline, Push()},
|
||||
{`\*/`, CommentMultiline, Pop(1)},
|
||||
{`[*/]`, CommentMultiline, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,56 @@
|
|||
package c
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Cfengine3 lexer.
|
||||
var Cfengine3 = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "CFEngine3",
|
||||
Aliases: []string{"cfengine3", "cf3"},
|
||||
Filenames: []string{"*.cf"},
|
||||
MimeTypes: []string{},
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`#.*?\n`, Comment, nil},
|
||||
{`(body)(\s+)(\S+)(\s+)(control)`, ByGroups(Keyword, Text, Keyword, Text, Keyword), nil},
|
||||
{`(body|bundle)(\s+)(\S+)(\s+)(\w+)(\()`, ByGroups(Keyword, Text, Keyword, Text, NameFunction, Punctuation), Push("arglist")},
|
||||
{`(body|bundle)(\s+)(\S+)(\s+)(\w+)`, ByGroups(Keyword, Text, Keyword, Text, NameFunction), nil},
|
||||
{`(")([^"]+)(")(\s+)(string|slist|int|real)(\s*)(=>)(\s*)`, ByGroups(Punctuation, NameVariable, Punctuation, Text, KeywordType, Text, Operator, Text), nil},
|
||||
{`(\S+)(\s*)(=>)(\s*)`, ByGroups(KeywordReserved, Text, Operator, Text), nil},
|
||||
{`"`, LiteralString, Push("string")},
|
||||
{`(\w+)(\()`, ByGroups(NameFunction, Punctuation), nil},
|
||||
{`([\w.!&|()]+)(::)`, ByGroups(NameClass, Punctuation), nil},
|
||||
{`(\w+)(:)`, ByGroups(KeywordDeclaration, Punctuation), nil},
|
||||
{`@[{(][^)}]+[})]`, NameVariable, nil},
|
||||
{`[(){},;]`, Punctuation, nil},
|
||||
{`=>`, Operator, nil},
|
||||
{`->`, Operator, nil},
|
||||
{`\d+\.\d+`, LiteralNumberFloat, nil},
|
||||
{`\d+`, LiteralNumberInteger, nil},
|
||||
{`\w+`, NameFunction, nil},
|
||||
{`\s+`, Text, nil},
|
||||
},
|
||||
"string": {
|
||||
{`\$[{(]`, LiteralStringInterpol, Push("interpol")},
|
||||
{`\\.`, LiteralStringEscape, nil},
|
||||
{`"`, LiteralString, Pop(1)},
|
||||
{`\n`, LiteralString, nil},
|
||||
{`.`, LiteralString, nil},
|
||||
},
|
||||
"interpol": {
|
||||
{`\$[{(]`, LiteralStringInterpol, Push()},
|
||||
{`[})]`, LiteralStringInterpol, Pop(1)},
|
||||
{`[^${()}]+`, LiteralStringInterpol, nil},
|
||||
},
|
||||
"arglist": {
|
||||
{`\)`, Punctuation, Pop(1)},
|
||||
{`,`, Punctuation, nil},
|
||||
{`\w+`, NameVariable, nil},
|
||||
{`\s+`, Text, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,63 @@
|
|||
package c
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Chaiscript lexer.
|
||||
var Chaiscript = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "ChaiScript",
|
||||
Aliases: []string{"chai", "chaiscript"},
|
||||
Filenames: []string{"*.chai"},
|
||||
MimeTypes: []string{"text/x-chaiscript", "application/x-chaiscript"},
|
||||
DotAll: true,
|
||||
},
|
||||
Rules{
|
||||
"commentsandwhitespace": {
|
||||
{`\s+`, Text, nil},
|
||||
{`//.*?\n`, CommentSingle, nil},
|
||||
{`/\*.*?\*/`, CommentMultiline, nil},
|
||||
{`^\#.*?\n`, CommentSingle, nil},
|
||||
},
|
||||
"slashstartsregex": {
|
||||
Include("commentsandwhitespace"),
|
||||
{`/(\\.|[^[/\\\n]|\[(\\.|[^\]\\\n])*])+/([gim]+\b|\B)`, LiteralStringRegex, Pop(1)},
|
||||
{`(?=/)`, Text, Push("#pop", "badregex")},
|
||||
Default(Pop(1)),
|
||||
},
|
||||
"badregex": {
|
||||
{`\n`, Text, Pop(1)},
|
||||
},
|
||||
"root": {
|
||||
Include("commentsandwhitespace"),
|
||||
{`\n`, Text, nil},
|
||||
{`[^\S\n]+`, Text, nil},
|
||||
{`\+\+|--|~|&&|\?|:|\|\||\\(?=\n)|\.\.(<<|>>>?|==?|!=?|[-<>+*%&|^/])=?`, Operator, Push("slashstartsregex")},
|
||||
{`[{(\[;,]`, Punctuation, Push("slashstartsregex")},
|
||||
{`[})\].]`, Punctuation, nil},
|
||||
{`[=+\-*/]`, Operator, nil},
|
||||
{`(for|in|while|do|break|return|continue|if|else|throw|try|catch)\b`, Keyword, Push("slashstartsregex")},
|
||||
{`(var)\b`, KeywordDeclaration, Push("slashstartsregex")},
|
||||
{`(attr|def|fun)\b`, KeywordReserved, nil},
|
||||
{`(true|false)\b`, KeywordConstant, nil},
|
||||
{`(eval|throw)\b`, NameBuiltin, nil},
|
||||
{"`\\S+`", NameBuiltin, nil},
|
||||
{`[$a-zA-Z_]\w*`, NameOther, nil},
|
||||
{`[0-9][0-9]*\.[0-9]+([eE][0-9]+)?[fd]?`, LiteralNumberFloat, nil},
|
||||
{`0x[0-9a-fA-F]+`, LiteralNumberHex, nil},
|
||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
||||
{`"`, LiteralStringDouble, Push("dqstring")},
|
||||
{`'(\\\\|\\'|[^'])*'`, LiteralStringSingle, nil},
|
||||
},
|
||||
"dqstring": {
|
||||
{`\$\{[^"}]+?\}`, LiteralStringInterpol, nil},
|
||||
{`\$`, LiteralStringDouble, nil},
|
||||
{`\\\\`, LiteralStringDouble, nil},
|
||||
{`\\"`, LiteralStringDouble, nil},
|
||||
{`[^\\"$]+`, LiteralStringDouble, nil},
|
||||
{`"`, LiteralStringDouble, Pop(1)},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,37 @@
|
|||
package c
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
. "github.com/alecthomas/chroma/lexers/p" // nolint
|
||||
)
|
||||
|
||||
// Cheetah lexer.
|
||||
var Cheetah = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Cheetah",
|
||||
Aliases: []string{"cheetah", "spitfire"},
|
||||
Filenames: []string{"*.tmpl", "*.spt"},
|
||||
MimeTypes: []string{"application/x-cheetah", "application/x-spitfire"},
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`(##[^\n]*)$`, ByGroups(Comment), nil},
|
||||
{`#[*](.|\n)*?[*]#`, Comment, nil},
|
||||
{`#end[^#\n]*(?:#|$)`, CommentPreproc, nil},
|
||||
{`#slurp$`, CommentPreproc, nil},
|
||||
{`(#[a-zA-Z]+)([^#\n]*)(#|$)`, ByGroups(CommentPreproc, Using(Python), CommentPreproc), nil},
|
||||
{`(\$)([a-zA-Z_][\w.]*\w)`, ByGroups(CommentPreproc, Using(Python)), nil},
|
||||
{`(\$\{!?)(.*?)(\})(?s)`, ByGroups(CommentPreproc, Using(Python), CommentPreproc), nil},
|
||||
{`(?sx)
|
||||
(.+?) # anything, followed by:
|
||||
(?:
|
||||
(?=\#[#a-zA-Z]*) | # an eval comment
|
||||
(?=\$[a-zA-Z_{]) | # a substitution
|
||||
\Z # end of string
|
||||
)
|
||||
`, Other, nil},
|
||||
{`\s+`, Text, nil},
|
||||
},
|
||||
},
|
||||
))
|
|
@ -0,0 +1,306 @@
|
|||
package c
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
var (
|
||||
clBuiltinFunctions = []string{
|
||||
"<", "<=", "=", ">", ">=", "-", "/", "/=", "*", "+", "1-", "1+",
|
||||
"abort", "abs", "acons", "acos", "acosh", "add-method", "adjoin",
|
||||
"adjustable-array-p", "adjust-array", "allocate-instance",
|
||||
"alpha-char-p", "alphanumericp", "append", "apply", "apropos",
|
||||
"apropos-list", "aref", "arithmetic-error-operands",
|
||||
"arithmetic-error-operation", "array-dimension", "array-dimensions",
|
||||
"array-displacement", "array-element-type", "array-has-fill-pointer-p",
|
||||
"array-in-bounds-p", "arrayp", "array-rank", "array-row-major-index",
|
||||
"array-total-size", "ash", "asin", "asinh", "assoc", "assoc-if",
|
||||
"assoc-if-not", "atan", "atanh", "atom", "bit", "bit-and", "bit-andc1",
|
||||
"bit-andc2", "bit-eqv", "bit-ior", "bit-nand", "bit-nor", "bit-not",
|
||||
"bit-orc1", "bit-orc2", "bit-vector-p", "bit-xor", "boole",
|
||||
"both-case-p", "boundp", "break", "broadcast-stream-streams",
|
||||
"butlast", "byte", "byte-position", "byte-size", "caaaar", "caaadr",
|
||||
"caaar", "caadar", "caaddr", "caadr", "caar", "cadaar", "cadadr",
|
||||
"cadar", "caddar", "cadddr", "caddr", "cadr", "call-next-method", "car",
|
||||
"cdaaar", "cdaadr", "cdaar", "cdadar", "cdaddr", "cdadr", "cdar",
|
||||
"cddaar", "cddadr", "cddar", "cdddar", "cddddr", "cdddr", "cddr", "cdr",
|
||||
"ceiling", "cell-error-name", "cerror", "change-class", "char", "char<",
|
||||
"char<=", "char=", "char>", "char>=", "char/=", "character",
|
||||
"characterp", "char-code", "char-downcase", "char-equal",
|
||||
"char-greaterp", "char-int", "char-lessp", "char-name",
|
||||
"char-not-equal", "char-not-greaterp", "char-not-lessp", "char-upcase",
|
||||
"cis", "class-name", "class-of", "clear-input", "clear-output",
|
||||
"close", "clrhash", "code-char", "coerce", "compile",
|
||||
"compiled-function-p", "compile-file", "compile-file-pathname",
|
||||
"compiler-macro-function", "complement", "complex", "complexp",
|
||||
"compute-applicable-methods", "compute-restarts", "concatenate",
|
||||
"concatenated-stream-streams", "conjugate", "cons", "consp",
|
||||
"constantly", "constantp", "continue", "copy-alist", "copy-list",
|
||||
"copy-pprint-dispatch", "copy-readtable", "copy-seq", "copy-structure",
|
||||
"copy-symbol", "copy-tree", "cos", "cosh", "count", "count-if",
|
||||
"count-if-not", "decode-float", "decode-universal-time", "delete",
|
||||
"delete-duplicates", "delete-file", "delete-if", "delete-if-not",
|
||||
"delete-package", "denominator", "deposit-field", "describe",
|
||||
"describe-object", "digit-char", "digit-char-p", "directory",
|
||||
"directory-namestring", "disassemble", "documentation", "dpb",
|
||||
"dribble", "echo-stream-input-stream", "echo-stream-output-stream",
|
||||
"ed", "eighth", "elt", "encode-universal-time", "endp",
|
||||
"enough-namestring", "ensure-directories-exist",
|
||||
"ensure-generic-function", "eq", "eql", "equal", "equalp", "error",
|
||||
"eval", "evenp", "every", "exp", "export", "expt", "fboundp",
|
||||
"fceiling", "fdefinition", "ffloor", "fifth", "file-author",
|
||||
"file-error-pathname", "file-length", "file-namestring",
|
||||
"file-position", "file-string-length", "file-write-date",
|
||||
"fill", "fill-pointer", "find", "find-all-symbols", "find-class",
|
||||
"find-if", "find-if-not", "find-method", "find-package", "find-restart",
|
||||
"find-symbol", "finish-output", "first", "float", "float-digits",
|
||||
"floatp", "float-precision", "float-radix", "float-sign", "floor",
|
||||
"fmakunbound", "force-output", "format", "fourth", "fresh-line",
|
||||
"fround", "ftruncate", "funcall", "function-keywords",
|
||||
"function-lambda-expression", "functionp", "gcd", "gensym", "gentemp",
|
||||
"get", "get-decoded-time", "get-dispatch-macro-character", "getf",
|
||||
"gethash", "get-internal-real-time", "get-internal-run-time",
|
||||
"get-macro-character", "get-output-stream-string", "get-properties",
|
||||
"get-setf-expansion", "get-universal-time", "graphic-char-p",
|
||||
"hash-table-count", "hash-table-p", "hash-table-rehash-size",
|
||||
"hash-table-rehash-threshold", "hash-table-size", "hash-table-test",
|
||||
"host-namestring", "identity", "imagpart", "import",
|
||||
"initialize-instance", "input-stream-p", "inspect",
|
||||
"integer-decode-float", "integer-length", "integerp",
|
||||
"interactive-stream-p", "intern", "intersection",
|
||||
"invalid-method-error", "invoke-debugger", "invoke-restart",
|
||||
"invoke-restart-interactively", "isqrt", "keywordp", "last", "lcm",
|
||||
"ldb", "ldb-test", "ldiff", "length", "lisp-implementation-type",
|
||||
"lisp-implementation-version", "list", "list*", "list-all-packages",
|
||||
"listen", "list-length", "listp", "load",
|
||||
"load-logical-pathname-translations", "log", "logand", "logandc1",
|
||||
"logandc2", "logbitp", "logcount", "logeqv", "logical-pathname",
|
||||
"logical-pathname-translations", "logior", "lognand", "lognor",
|
||||
"lognot", "logorc1", "logorc2", "logtest", "logxor", "long-site-name",
|
||||
"lower-case-p", "machine-instance", "machine-type", "machine-version",
|
||||
"macroexpand", "macroexpand-1", "macro-function", "make-array",
|
||||
"make-broadcast-stream", "make-concatenated-stream", "make-condition",
|
||||
"make-dispatch-macro-character", "make-echo-stream", "make-hash-table",
|
||||
"make-instance", "make-instances-obsolete", "make-list",
|
||||
"make-load-form", "make-load-form-saving-slots", "make-package",
|
||||
"make-pathname", "make-random-state", "make-sequence", "make-string",
|
||||
"make-string-input-stream", "make-string-output-stream", "make-symbol",
|
||||
"make-synonym-stream", "make-two-way-stream", "makunbound", "map",
|
||||
"mapc", "mapcan", "mapcar", "mapcon", "maphash", "map-into", "mapl",
|
||||
"maplist", "mask-field", "max", "member", "member-if", "member-if-not",
|
||||
"merge", "merge-pathnames", "method-combination-error",
|
||||
"method-qualifiers", "min", "minusp", "mismatch", "mod",
|
||||
"muffle-warning", "name-char", "namestring", "nbutlast", "nconc",
|
||||
"next-method-p", "nintersection", "ninth", "no-applicable-method",
|
||||
"no-next-method", "not", "notany", "notevery", "nreconc", "nreverse",
|
||||
"nset-difference", "nset-exclusive-or", "nstring-capitalize",
|
||||
"nstring-downcase", "nstring-upcase", "nsublis", "nsubst", "nsubst-if",
|
||||
"nsubst-if-not", "nsubstitute", "nsubstitute-if", "nsubstitute-if-not",
|
||||
"nth", "nthcdr", "null", "numberp", "numerator", "nunion", "oddp",
|
||||
"open", "open-stream-p", "output-stream-p", "package-error-package",
|
||||
"package-name", "package-nicknames", "packagep",
|
||||
"package-shadowing-symbols", "package-used-by-list", "package-use-list",
|
||||
"pairlis", "parse-integer", "parse-namestring", "pathname",
|
||||
"pathname-device", "pathname-directory", "pathname-host",
|
||||
"pathname-match-p", "pathname-name", "pathnamep", "pathname-type",
|
||||
"pathname-version", "peek-char", "phase", "plusp", "position",
|
||||
"position-if", "position-if-not", "pprint", "pprint-dispatch",
|
||||
"pprint-fill", "pprint-indent", "pprint-linear", "pprint-newline",
|
||||
"pprint-tab", "pprint-tabular", "prin1", "prin1-to-string", "princ",
|
||||
"princ-to-string", "print", "print-object", "probe-file", "proclaim",
|
||||
"provide", "random", "random-state-p", "rassoc", "rassoc-if",
|
||||
"rassoc-if-not", "rational", "rationalize", "rationalp", "read",
|
||||
"read-byte", "read-char", "read-char-no-hang", "read-delimited-list",
|
||||
"read-from-string", "read-line", "read-preserving-whitespace",
|
||||
"read-sequence", "readtable-case", "readtablep", "realp", "realpart",
|
||||
"reduce", "reinitialize-instance", "rem", "remhash", "remove",
|
||||
"remove-duplicates", "remove-if", "remove-if-not", "remove-method",
|
||||
"remprop", "rename-file", "rename-package", "replace", "require",
|
||||
"rest", "restart-name", "revappend", "reverse", "room", "round",
|
||||
"row-major-aref", "rplaca", "rplacd", "sbit", "scale-float", "schar",
|
||||
"search", "second", "set", "set-difference",
|
||||
"set-dispatch-macro-character", "set-exclusive-or",
|
||||
"set-macro-character", "set-pprint-dispatch", "set-syntax-from-char",
|
||||
"seventh", "shadow", "shadowing-import", "shared-initialize",
|
||||
"short-site-name", "signal", "signum", "simple-bit-vector-p",
|
||||
"simple-condition-format-arguments", "simple-condition-format-control",
|
||||
"simple-string-p", "simple-vector-p", "sin", "sinh", "sixth", "sleep",
|
||||
"slot-boundp", "slot-exists-p", "slot-makunbound", "slot-missing",
|
||||
"slot-unbound", "slot-value", "software-type", "software-version",
|
||||
"some", "sort", "special-operator-p", "sqrt", "stable-sort",
|
||||
"standard-char-p", "store-value", "stream-element-type",
|
||||
"stream-error-stream", "stream-external-format", "streamp", "string",
|
||||
"string<", "string<=", "string=", "string>", "string>=", "string/=",
|
||||
"string-capitalize", "string-downcase", "string-equal",
|
||||
"string-greaterp", "string-left-trim", "string-lessp",
|
||||
"string-not-equal", "string-not-greaterp", "string-not-lessp",
|
||||
"stringp", "string-right-trim", "string-trim", "string-upcase",
|
||||
"sublis", "subseq", "subsetp", "subst", "subst-if", "subst-if-not",
|
||||
"substitute", "substitute-if", "substitute-if-not", "subtypep", "svref",
|
||||
"sxhash", "symbol-function", "symbol-name", "symbolp", "symbol-package",
|
||||
"symbol-plist", "symbol-value", "synonym-stream-symbol", "syntax:",
|
||||
"tailp", "tan", "tanh", "tenth", "terpri", "third",
|
||||
"translate-logical-pathname", "translate-pathname", "tree-equal",
|
||||
"truename", "truncate", "two-way-stream-input-stream",
|
||||
"two-way-stream-output-stream", "type-error-datum",
|
||||
"type-error-expected-type", "type-of", "typep", "unbound-slot-instance",
|
||||
"unexport", "unintern", "union", "unread-char", "unuse-package",
|
||||
"update-instance-for-different-class",
|
||||
"update-instance-for-redefined-class", "upgraded-array-element-type",
|
||||
"upgraded-complex-part-type", "upper-case-p", "use-package",
|
||||
"user-homedir-pathname", "use-value", "values", "values-list", "vector",
|
||||
"vectorp", "vector-pop", "vector-push", "vector-push-extend", "warn",
|
||||
"wild-pathname-p", "write", "write-byte", "write-char", "write-line",
|
||||
"write-sequence", "write-string", "write-to-string", "yes-or-no-p",
|
||||
"y-or-n-p", "zerop",
|
||||
}
|
||||
|
||||
clSpecialForms = []string{
|
||||
"block", "catch", "declare", "eval-when", "flet", "function", "go", "if",
|
||||
"labels", "lambda", "let", "let*", "load-time-value", "locally", "macrolet",
|
||||
"multiple-value-call", "multiple-value-prog1", "progn", "progv", "quote",
|
||||
"return-from", "setq", "symbol-macrolet", "tagbody", "the", "throw",
|
||||
"unwind-protect",
|
||||
}
|
||||
|
||||
clMacros = []string{
|
||||
"and", "assert", "call-method", "case", "ccase", "check-type", "cond",
|
||||
"ctypecase", "decf", "declaim", "defclass", "defconstant", "defgeneric",
|
||||
"define-compiler-macro", "define-condition", "define-method-combination",
|
||||
"define-modify-macro", "define-setf-expander", "define-symbol-macro",
|
||||
"defmacro", "defmethod", "defpackage", "defparameter", "defsetf",
|
||||
"defstruct", "deftype", "defun", "defvar", "destructuring-bind", "do",
|
||||
"do*", "do-all-symbols", "do-external-symbols", "dolist", "do-symbols",
|
||||
"dotimes", "ecase", "etypecase", "formatter", "handler-bind",
|
||||
"handler-case", "ignore-errors", "incf", "in-package", "lambda", "loop",
|
||||
"loop-finish", "make-method", "multiple-value-bind", "multiple-value-list",
|
||||
"multiple-value-setq", "nth-value", "or", "pop",
|
||||
"pprint-exit-if-list-exhausted", "pprint-logical-block", "pprint-pop",
|
||||
"print-unreadable-object", "prog", "prog*", "prog1", "prog2", "psetf",
|
||||
"psetq", "push", "pushnew", "remf", "restart-bind", "restart-case",
|
||||
"return", "rotatef", "setf", "shiftf", "step", "time", "trace", "typecase",
|
||||
"unless", "untrace", "when", "with-accessors", "with-compilation-unit",
|
||||
"with-condition-restarts", "with-hash-table-iterator",
|
||||
"with-input-from-string", "with-open-file", "with-open-stream",
|
||||
"with-output-to-string", "with-package-iterator", "with-simple-restart",
|
||||
"with-slots", "with-standard-io-syntax",
|
||||
}
|
||||
|
||||
clLambdaListKeywords = []string{
|
||||
"&allow-other-keys", "&aux", "&body", "&environment", "&key", "&optional",
|
||||
"&rest", "&whole",
|
||||
}
|
||||
|
||||
clDeclarations = []string{
|
||||
"dynamic-extent", "ignore", "optimize", "ftype", "inline", "special",
|
||||
"ignorable", "notinline", "type",
|
||||
}
|
||||
|
||||
clBuiltinTypes = []string{
|
||||
"atom", "boolean", "base-char", "base-string", "bignum", "bit",
|
||||
"compiled-function", "extended-char", "fixnum", "keyword", "nil",
|
||||
"signed-byte", "short-float", "single-float", "double-float", "long-float",
|
||||
"simple-array", "simple-base-string", "simple-bit-vector", "simple-string",
|
||||
"simple-vector", "standard-char", "unsigned-byte",
|
||||
|
||||
// Condition Types
|
||||
"arithmetic-error", "cell-error", "condition", "control-error",
|
||||
"division-by-zero", "end-of-file", "error", "file-error",
|
||||
"floating-point-inexact", "floating-point-overflow",
|
||||
"floating-point-underflow", "floating-point-invalid-operation",
|
||||
"parse-error", "package-error", "print-not-readable", "program-error",
|
||||
"reader-error", "serious-condition", "simple-condition", "simple-error",
|
||||
"simple-type-error", "simple-warning", "stream-error", "storage-condition",
|
||||
"style-warning", "type-error", "unbound-variable", "unbound-slot",
|
||||
"undefined-function", "warning",
|
||||
}
|
||||
|
||||
clBuiltinClasses = []string{
|
||||
"array", "broadcast-stream", "bit-vector", "built-in-class", "character",
|
||||
"class", "complex", "concatenated-stream", "cons", "echo-stream",
|
||||
"file-stream", "float", "function", "generic-function", "hash-table",
|
||||
"integer", "list", "logical-pathname", "method-combination", "method",
|
||||
"null", "number", "package", "pathname", "ratio", "rational", "readtable",
|
||||
"real", "random-state", "restart", "sequence", "standard-class",
|
||||
"standard-generic-function", "standard-method", "standard-object",
|
||||
"string-stream", "stream", "string", "structure-class", "structure-object",
|
||||
"symbol", "synonym-stream", "t", "two-way-stream", "vector",
|
||||
}
|
||||
)
|
||||
|
||||
// Common Lisp lexer.
|
||||
var CommonLisp = internal.Register(TypeRemappingLexer(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Common Lisp",
|
||||
Aliases: []string{"common-lisp", "cl", "lisp"},
|
||||
Filenames: []string{"*.cl", "*.lisp"},
|
||||
MimeTypes: []string{"text/x-common-lisp"},
|
||||
CaseInsensitive: true,
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
Default(Push("body")),
|
||||
},
|
||||
"multiline-comment": {
|
||||
{`#\|`, CommentMultiline, Push()},
|
||||
{`\|#`, CommentMultiline, Pop(1)},
|
||||
{`[^|#]+`, CommentMultiline, nil},
|
||||
{`[|#]`, CommentMultiline, nil},
|
||||
},
|
||||
"commented-form": {
|
||||
{`\(`, CommentPreproc, Push()},
|
||||
{`\)`, CommentPreproc, Pop(1)},
|
||||
{`[^()]+`, CommentPreproc, nil},
|
||||
},
|
||||
"body": {
|
||||
{`\s+`, Text, nil},
|
||||
{`;.*$`, CommentSingle, nil},
|
||||
{`#\|`, CommentMultiline, Push("multiline-comment")},
|
||||
{`#\d*Y.*$`, CommentSpecial, nil},
|
||||
{`"(\\.|\\\n|[^"\\])*"`, LiteralString, nil},
|
||||
{`:(\|[^|]+\||(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~])(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~]|[#.:])*)`, LiteralStringSymbol, nil},
|
||||
{`::(\|[^|]+\||(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~])(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~]|[#.:])*)`, LiteralStringSymbol, nil},
|
||||
{`:#(\|[^|]+\||(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~])(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~]|[#.:])*)`, LiteralStringSymbol, nil},
|
||||
{`'(\|[^|]+\||(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~])(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~]|[#.:])*)`, LiteralStringSymbol, nil},
|
||||
{`'`, Operator, nil},
|
||||
{"`", Operator, nil},
|
||||
{"[-+]?\\d+\\.?(?=[ \"()\\'\\n,;`])", LiteralNumberInteger, nil},
|
||||
{"[-+]?\\d+/\\d+(?=[ \"()\\'\\n,;`])", LiteralNumber, nil},
|
||||
{"[-+]?(\\d*\\.\\d+([defls][-+]?\\d+)?|\\d+(\\.\\d*)?[defls][-+]?\\d+)(?=[ \"()\\'\\n,;`])", LiteralNumberFloat, nil},
|
||||
{"#\\\\.(?=[ \"()\\'\\n,;`])", LiteralStringChar, nil},
|
||||
{`#\\(\|[^|]+\||(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~])(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~]|[#.:])*)`, LiteralStringChar, nil},
|
||||
{`#\(`, Operator, Push("body")},
|
||||
{`#\d*\*[01]*`, LiteralOther, nil},
|
||||
{`#:(\|[^|]+\||(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~])(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~]|[#.:])*)`, LiteralStringSymbol, nil},
|
||||
{`#[.,]`, Operator, nil},
|
||||
{`#\'`, NameFunction, nil},
|
||||
{`#b[+-]?[01]+(/[01]+)?`, LiteralNumberBin, nil},
|
||||
{`#o[+-]?[0-7]+(/[0-7]+)?`, LiteralNumberOct, nil},
|
||||
{`#x[+-]?[0-9a-f]+(/[0-9a-f]+)?`, LiteralNumberHex, nil},
|
||||
{`#\d+r[+-]?[0-9a-z]+(/[0-9a-z]+)?`, LiteralNumber, nil},
|
||||
{`(#c)(\()`, ByGroups(LiteralNumber, Punctuation), Push("body")},
|
||||
{`(#\d+a)(\()`, ByGroups(LiteralOther, Punctuation), Push("body")},
|
||||
{`(#s)(\()`, ByGroups(LiteralOther, Punctuation), Push("body")},
|
||||
{`#p?"(\\.|[^"])*"`, LiteralOther, nil},
|
||||
{`#\d+=`, Operator, nil},
|
||||
{`#\d+#`, Operator, nil},
|
||||
{"#+nil(?=[ \"()\\'\\n,;`])\\s*\\(", CommentPreproc, Push("commented-form")},
|
||||
{`#[+-]`, Operator, nil},
|
||||
{`(,@|,|\.)`, Operator, nil},
|
||||
{"(t|nil)(?=[ \"()\\'\\n,;`])", NameConstant, nil},
|
||||
{`\*(\|[^|]+\||(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~])(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~]|[#.:])*)\*`, NameVariableGlobal, nil},
|
||||
{`(\|[^|]+\||(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~])(?:\\.|[\w!$%&*+-/<=>?@\[\]^{}~]|[#.:])*)`, NameVariable, nil},
|
||||
{`\(`, Punctuation, Push("body")},
|
||||
{`\)`, Punctuation, Pop(1)},
|
||||
},
|
||||
},
|
||||
), TypeMapping{
|
||||
{NameVariable, NameFunction, clBuiltinFunctions},
|
||||
{NameVariable, Keyword, clSpecialForms},
|
||||
{NameVariable, NameBuiltin, clMacros},
|
||||
{NameVariable, Keyword, clLambdaListKeywords},
|
||||
{NameVariable, Keyword, clDeclarations},
|
||||
{NameVariable, KeywordType, clBuiltinTypes},
|
||||
{NameVariable, NameClass, clBuiltinClasses},
|
||||
}))
|
|
@ -0,0 +1,38 @@
|
|||
package c
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma" // nolint
|
||||
"github.com/alecthomas/chroma/lexers/internal"
|
||||
)
|
||||
|
||||
// Clojure lexer.
|
||||
var Clojure = internal.Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Clojure",
|
||||
Aliases: []string{"clojure", "clj"},
|
||||
Filenames: []string{"*.clj"},
|
||||
MimeTypes: []string{"text/x-clojure", "application/x-clojure"},
|
||||
},
|
||||
Rules{
|
||||
"root": {
|
||||
{`;.*$`, CommentSingle, nil},
|
||||
{`[,\s]+`, Text, nil},
|
||||
{`-?\d+\.\d+`, LiteralNumberFloat, nil},
|
||||
{`-?\d+`, LiteralNumberInteger, nil},
|
||||
{`0x-?[abcdef\d]+`, LiteralNumberHex, nil},
|
||||
{`"(\\\\|\\"|[^"])*"`, LiteralString, nil},
|
||||
{`'(?!#)[\w!$%*+<=>?/.#-]+`, LiteralStringSymbol, nil},
|
||||
{`\\(.|[a-z]+)`, LiteralStringChar, nil},
|
||||
{`::?#?(?!#)[\w!$%*+<=>?/.#-]+`, LiteralStringSymbol, nil},
|
||||
{"~@|[`\\'#^~&@]", Operator, nil},
|
||||
{Words(``, ` `, `.`, `def`, `do`, `fn`, `if`, `let`, `new`, `quote`, `var`, `loop`), Keyword, nil},
|
||||
{Words(``, ` `, `def-`, `defn`, `defn-`, `defmacro`, `defmulti`, `defmethod`, `defstruct`, `defonce`, `declare`, `definline`, `definterface`, `defprotocol`, `defrecord`, `deftype`, `defproject`, `ns`), KeywordDeclaration, nil},
|
||||
{Words(``, ` `, `*`, `+`, `-`, `->`, `/`, `<`, `<=`, `=`, `==`, `>`, `>=`, `..`, `accessor`, `agent`, `agent-errors`, `aget`, `alength`, `all-ns`, `alter`, `and`, `append-child`, `apply`, `array-map`, `aset`, `aset-boolean`, `aset-byte`, `aset-char`, `aset-double`, `aset-float`, `aset-int`, `aset-long`, `aset-short`, `assert`, `assoc`, `await`, `await-for`, `bean`, `binding`, `bit-and`, `bit-not`, `bit-or`, `bit-shift-left`, `bit-shift-right`, `bit-xor`, `boolean`, `branch?`, `butlast`, `byte`, `cast`, `char`, `children`, `class`, `clear-agent-errors`, `comment`, `commute`, `comp`, `comparator`, `complement`, `concat`, `conj`, `cons`, `constantly`, `cond`, `if-not`, `construct-proxy`, `contains?`, `count`, `create-ns`, `create-struct`, `cycle`, `dec`, `deref`, `difference`, `disj`, `dissoc`, `distinct`, `doall`, `doc`, `dorun`, `doseq`, `dosync`, `dotimes`, `doto`, `double`, `down`, `drop`, `drop-while`, `edit`, `end?`, `ensure`, `eval`, `every?`, `false?`, `ffirst`, `file-seq`, `filter`, `find`, `find-doc`, `find-ns`, `find-var`, `first`, `float`, `flush`, `for`, `fnseq`, `frest`, `gensym`, `get-proxy-class`, `get`, `hash-map`, `hash-set`, `identical?`, `identity`, `if-let`, `import`, `in-ns`, `inc`, `index`, `insert-child`, `insert-left`, `insert-right`, `inspect-table`, `inspect-tree`, `instance?`, `int`, `interleave`, `intersection`, `into`, `into-array`, `iterate`, `join`, `key`, `keys`, `keyword`, `keyword?`, `last`, `lazy-cat`, `lazy-cons`, `left`, `lefts`, `line-seq`, `list*`, `list`, `load`, `load-file`, `locking`, `long`, `loop`, `macroexpand`, `macroexpand-1`, `make-array`, `make-node`, `map`, `map-invert`, `map?`, `mapcat`, `max`, `max-key`, `memfn`, `merge`, `merge-with`, `meta`, `min`, `min-key`, `name`, `namespace`, `neg?`, `new`, `newline`, `next`, `nil?`, `node`, `not`, `not-any?`, `not-every?`, `not=`, `ns-imports`, `ns-interns`, `ns-map`, `ns-name`, `ns-publics`, `ns-refers`, `ns-resolve`, `ns-unmap`, `nth`, `nthrest`, `or`, `parse`, `partial`, `path`, `peek`, `pop`, `pos?`, `pr`, `pr-str`, `print`, `print-str`, `println`, `println-str`, `prn`, `prn-str`, `project`, `proxy`, `proxy-mappings`, `quot`, `rand`, `rand-int`, `range`, `re-find`, `re-groups`, `re-matcher`, `re-matches`, `re-pattern`, `re-seq`, `read`, `read-line`, `reduce`, `ref`, `ref-set`, `refer`, `rem`, `remove`, `remove-method`, `remove-ns`, `rename`, `rename-keys`, `repeat`, `replace`, `replicate`, `resolve`, `rest`, `resultset-seq`, `reverse`, `rfirst`, `right`, `rights`, `root`, `rrest`, `rseq`, `second`, `select`, `select-keys`, `send`, `send-off`, `seq`, `seq-zip`, `seq?`, `set`, `short`, `slurp`, `some`, `sort`, `sort-by`, `sorted-map`, `sorted-map-by`, `sorted-set`, `special-symbol?`, `split-at`, `split-with`, `str`, `string?`, `struct`, `struct-map`, `subs`, `subvec`, `symbol`, `symbol?`, `sync`, `take`, `take-nth`, `take-while`, `test`, `time`, `to-array`, `to-array-2d`, `tree-seq`, `true?`, `union`, `up`, `update-proxy`, `val`, `vals`, `var-get`, `var-set`, `var?`, `vector`, `vector-zip`, `vector?`, `when`, `when-first`, `when-let`, `when-not`, `with-local-vars`, `with-meta`, `with-open`, `with-out-str`, `xml-seq`, `xml-zip`, `zero?`, `zipmap`, `zipper`), NameBuiltin, nil},
|
||||
{`(?<=\()(?!#)[\w!$%*+<=>?/.#-]+`, NameFunction, nil},
|
||||
{`(?!#)[\w!$%*+<=>?/.#-]+`, NameVariable, nil},
|
||||
{`(\[|\])`, Punctuation, nil},
|
||||
{`(\{|\})`, Punctuation, nil},
|
||||
{`(\(|\))`, Punctuation, nil},
|
||||
},
|
||||
},
|
||||
))
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue