From 8557e18ed58dbaf5b42d740bc0837945801ccb59 Mon Sep 17 00:00:00 2001 From: Romain Beauxis Date: Thu, 5 Oct 2023 01:20:17 -0500 Subject: [PATCH 1/2] Add liquidsoap --- .gitmodules | 3 + grammars.yml | 2 + lib/linguist/languages.yml | 8 + samples/Liquidsoap/audio.liq | 348 ++++++++++++++++++ samples/Liquidsoap/http.liq | 280 ++++++++++++++ samples/Liquidsoap/playlist.liq | 224 +++++++++++ samples/Liquidsoap/utils.liq | 67 ++++ vendor/README.md | 1 + vendor/grammars/vscode-liquidsoap | 1 + .../git_submodule/vscode-liquidsoap.dep.yml | 33 ++ 10 files changed, 967 insertions(+) create mode 100644 samples/Liquidsoap/audio.liq create mode 100644 samples/Liquidsoap/http.liq create mode 100644 samples/Liquidsoap/playlist.liq create mode 100644 samples/Liquidsoap/utils.liq create mode 160000 vendor/grammars/vscode-liquidsoap create mode 100644 vendor/licenses/git_submodule/vscode-liquidsoap.dep.yml diff --git a/.gitmodules b/.gitmodules index e14f0bf03f..ce7f84f4ac 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1245,6 +1245,9 @@ [submodule "vendor/grammars/vscode-lean"] path = vendor/grammars/vscode-lean url = https://github.com/leanprover/vscode-lean +[submodule "vendor/grammars/vscode-liquidsoap"] + path = vendor/grammars/vscode-liquidsoap + url = https://github.com/savonet/vscode-liquidsoap.git [submodule "vendor/grammars/vscode-monkey-c"] path = vendor/grammars/vscode-monkey-c url = https://github.com/ghisguth/vscode-monkey-c diff --git a/grammars.yml b/grammars.yml index cf9da731c7..41b7e39af0 100644 --- a/grammars.yml +++ b/grammars.yml @@ -1119,6 +1119,8 @@ vendor/grammars/vscode-lean: - markdown.lean.codeblock - source.lean - source.lean.markdown +vendor/grammars/vscode-liquidsoap: +- source.liquidsoap vendor/grammars/vscode-monkey-c: - source.mc vendor/grammars/vscode-motoko: diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 88c425287f..4c1c5b885e 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -3762,6 +3762,14 @@ Linux Kernel Module: tm_scope: none ace_mode: text language_id: 203 +Liquidsoap: + type: programming + color: "#990066" + extensions: + - ".liq" + tm_scope: source.liquidsoap + ace_mode: text + language_id: 614641732 Liquid: type: markup color: "#67b8de" diff --git a/samples/Liquidsoap/audio.liq b/samples/Liquidsoap/audio.liq new file mode 100644 index 0000000000..2cba122569 --- /dev/null +++ b/samples/Liquidsoap/audio.liq @@ -0,0 +1,348 @@ +# Multiband compression. The list in argument specifies +# - the `frequency` below which we should apply compression (it is above previous band) +# - the `attack` time (ms) +# - the `release` time (ms) +# - the compression `ratio` +# - the `threshold` for compression +# - the `gain` for the band +# @category Source / Audio processing +# @param ~limit Also apply limiting to bands. +# @param l Parameters for compression bands. +# @param s Source on which multiband compression should be applied. +# @flag extra +def compress.multiband(~limit=true, ~wet=getter(1.), s, l) = + # Check that the bands are with increasing frequencies. + for + i + = + 0 + to + list.length(l) - 2 + do + if + getter.get(list.nth(l, i + 1).frequency) < + getter.get(list.nth(l, i).frequency) + then + failwith("Bands should be sorted.") + end + end + + # Process a band + def band(low, band) = + high = + if + getter.is_constant(band.frequency) + and + getter.get(band.frequency) >= float_of_int(audio.samplerate()) / 2. + then + infinity + else + band.frequency + end + + s = filter.iir.eq.low_high(low=low, high=high, s) + s = + compress( + attack=band.attack, + release=band.release, + threshold=band.threshold, + ratio=band.ratio, + gain=band.gain, + s + ) + + if limit then limiter(s) else s end + end + + ls = + list.mapi( + fun (i, b) -> + band(if i == 0 then 0. else list.nth(l, i - 1).frequency end, b), + l + ) + + c = add(normalize=false, ls) + s = + if + not getter.is_constant(wet) or getter.get(wet) != 1. + then + add( + normalize=false, [amplify({1. - getter.get(wet)}, s), amplify(wet, c)] + ) + else + c + end + + + # Seal l element type + if false then () else list.hd(l) end + + # Limit to avoid bad surprises + limiter(s) +end + +# Compress and normalize, producing a more uniform and "full" sound. +# @category Source / Audio processing +# @flag extra +# @param s The input source. +def nrj(s) = + compress(threshold=-15., ratio=3., gain=3., normalize(s)) +end + +# Multiband-compression. +# @category Source / Audio processing +# @flag extra +# @param s The input source. +def sky(s) = + # 3-band crossover + low = fun (s) -> filter.iir.eq.low(frequency=168., s) + mh = fun (s) -> filter.iir.eq.high(frequency=100., s) + mid = fun (s) -> filter.iir.eq.low(frequency=1800., s) + high = fun (s) -> filter.iir.eq.high(frequency=1366., s) + + # Add back + add( + normalize=false, + [ + compress( + attack=100., + release=200., + threshold=-20., + ratio=6., + gain=6.7, + knee=0.3, + low(s) + ), + compress( + attack=100., + release=200., + threshold=-20., + ratio=6., + gain=6.7, + knee=0.3, + mid(mh(s)) + ), + compress( + attack=100., + release=200., + threshold=-20., + ratio=6., + gain=6.7, + knee=0.3, + high(s) + ) + ] + ) +end + +# Generate DTMF tones. +# @flag extra +# @category Source / Sound synthesis +# @param ~duration Duration of a tone (in seconds). +# @param ~delay Dealy between two successive tones (in seconds). +# @param dtmf String describing DTMF tones to generates: it should contains characters 0 to 9, A to D, or * or #. +def replaces dtmf(~duration=0.1, ~delay=0.05, dtmf) = + l = ref([]) + for + i + = + 0 + to + string.length(dtmf) - 1 + do + c = string.sub(dtmf, start=i, length=1) + let (row, col) = + if + c == "1" + then + (697., 1209.) + elsif c == "2" then (697., 1336.) + elsif c == "3" then (697., 1477.) + elsif c == "A" then (697., 1633.) + elsif c == "4" then (770., 1209.) + elsif c == "5" then (770., 1336.) + elsif c == "6" then (770., 1477.) + elsif c == "B" then (770., 1633.) + elsif c == "7" then (852., 1209.) + elsif c == "8" then (852., 1336.) + elsif c == "9" then (852., 1477.) + elsif c == "C" then (852., 1633.) + elsif c == "*" then (941., 1209.) + elsif c == "0" then (941., 1336.) + elsif c == "#" then (941., 1477.) + elsif c == "D" then (941., 1633.) + else + (0., 0.) + end + + s = add([sine(row, duration=duration), sine(col, duration=duration)]) + l := blank(duration=delay)::l() + l := s::l() + end + + l = list.rev(l()) + sequence(l) +end + +# Mixing table controllable via source methods and optional +# server/telnet commands. +# @flag extra +# @category Source / Audio processing +# @param ~id Force the value of the source ID. +# @param ~register_server_commands Register corresponding server commands +def mix(~id=null(), ~register_server_commands=true, sources) = + id = string.id.default(default="mixer", id) + inputs = + list.map( + fun (s) -> + begin + volume = ref(1.) + is_selected = ref(false) + is_single = ref(false) + {volume=volume, selected=is_selected, single=is_single, source=s} + end, + sources + ) + + insert_metadata_fn = ref(fun (_) -> ()) + sources = + list.map( + fun (input) -> + begin + s = amplify(input.volume, input.source) + s = + source.on_track( + s, fun (_) -> if input.single() then input.selected := false end + ) + + s = + source.on_metadata( + s, + fun (m) -> + begin + fn = insert_metadata_fn() + fn(m) + end + ) + + switch([(input.selected, s)]) + end, + inputs + ) + + s = add(sources) + let {metadata = _, ...tracks} = source.tracks(s) + s = source(tracks) + s = insert_metadata(s) + insert_metadata_fn := s.insert_metadata + let {track_marks = _, ...tracks} = source.tracks(s) + s = source(id=id, tracks) + if + register_server_commands + then + def status(input) = + "ready=#{source.is_ready( + input.source + )} selected=#{input.selected()} single=#{input.single()} volume=#{int_of_float( + input.volume() * 100. + )}% remaining=#{source.remaining(input.source)}" + end + + server.register( + namespace=source.id(s), + description="Skip current track on all enabled sources.", + "skip", + fun (_) -> + begin + list.iter( + fun (input) -> + if input.selected() then source.skip(input.source) end, + inputs + ) + + "OK" + end + ) + + server.register( + namespace=source.id(s), + description="Set volume for a given source.", + usage="volume ", + "volume", + fun (v) -> + begin + try + let [i, v] = r/\s/.split(v) + input = list.nth(inputs, int_of_string(i)) + input.volume := float_of_string(v) + status(input) + catch _ do + "Usage: volume " + end + end + ) + + server.register( + namespace=source.id(s), + description="Enable/disable a source.", + usage="select ", + "select", + fun (arg) -> + begin + try + let [i, b] = r/\s/.split(arg) + input = list.nth(inputs, int_of_string(i)) + input.selected := (b == "true") + status(input) + catch _ do + "Usage: select " + end + end + ) + + server.register( + namespace=source.id(s), + description="Enable/disable automatic stop at the end of track.", + usage="single ", + "single", + fun (arg) -> + begin + try + let [i, b] = r/\s/.split(arg) + input = list.nth(inputs, int_of_string(i)) + input.single := (b == "true") + status(input) + catch _ do + "Usage: single " + end + end + ) + + server.register( + namespace=source.id(s), + description="Display current status.", + "status", + fun (i) -> + begin + try + status(list.nth(inputs, int_of_string(i))) + catch _ do + "Usage: status " + end + end + ) + + server.register( + namespace=source.id(s), + description="Print the list of input sources.", + "inputs", + fun (_) -> + string.concat( + separator=" ", + list.map(fun (input) -> source.id(input.source), inputs) + ) + ) + end + + s.{inputs=inputs} +end \ No newline at end of file diff --git a/samples/Liquidsoap/http.liq b/samples/Liquidsoap/http.liq new file mode 100644 index 0000000000..46149333fe --- /dev/null +++ b/samples/Liquidsoap/http.liq @@ -0,0 +1,280 @@ +# Prepare a list of data to be sent as multipart form data. +# @category Internet +# @param ~boundary Specify boundary to use for multipart/form-data. +# @param data data to insert +def http.multipart_form_data(~boundary=null(), data) = + def default_boundary() = + range = [...string.char.ascii.alphabet, ...string.char.ascii.number] + l = list.init(12, fun (_) -> string.char.ascii.random(range)) + string.concat(l) + end + + boundary = null.default(boundary, default_boundary) + + def mk_content(contents, entry) = + data = entry.contents + attributes = [("name", entry.name), ...entry.attributes] + attributes = + list.map( + fun (v) -> "#{string(fst(v))}=#{string.quote(snd(v))}", attributes + ) + + attributes = string.concat(separator="; ", attributes) + headers = + list.map(fun (v) -> "#{string(fst(v))}: #{string(snd(v))}", entry.headers) + + headers = string.concat(separator="\r\n", headers) + headers = headers == "" ? "" : "#{headers}\r\n" + + # This is for typing purposes + (entry : unit) + [ + ...contents, + getter("--#{boundary}\r\n"), + getter("Content-Disposition: form-data; #{attributes}\r\n"), + getter(headers), + getter("\r\n"), + data, + getter("\r\n") + ] + end + + contents = [...list.fold(mk_content, [], data), getter("--#{boundary}--\r\n")] + contents = string.getter.concat(contents) + contents = + if + list.for_all(fun (entry) -> getter.is_constant(entry.contents), data) + then + getter(string.getter.flush(contents)) + else + contents + end + + {contents=contents, boundary=boundary} +end + +# Initiate a response handler with pre-filled values. +# @category Internet +# @method content_type Set `"Content-Type"` header +# @method data Set response data. +# @method headers Replace response headers. +# @method header Set a single header on the response +# @method json Set content-type to json and data to `json.stringify` of the argument +# @method redirect Set `status_code` and `Location:` header for a HTTP redirect response +# @method html Set content-type to html and data to argument value +# @method http_version Set http protocol version +# @method status_code Set response status code +# @method status_message Set response status message +def http.response( + ~http_version="1.1", + ~status_code=null(), + ~status_message=null(), + ~headers=[], + ~content_type=null(), + ~data=getter("") +) = + status_code = + status_code + ?? + if + http_version == "1.1" + and + headers["expect"] == "100-continue" + and + getter.get(data) == "" + then + 100 + else + 200 + end + + http_version = ref(http_version) + status_code = ref(status_code) + status_message = ref(status_message) + headers = ref(headers) + content_type = ref(content_type) + data = ref(data) + status_sent = ref(false) + headers_sent = ref(false) + data_sent = ref(false) + response_ended = ref(false) + + def mk_status() = + status_sent := true + http_version = http_version() + status_code = status_code() + status_code = + if + status_code == 100 and getter.get(data()) != "" + then + 200 + else + status_code + end + + status_message = status_message() ?? http.codes[status_code] + "HTTP/#{http_version} #{status_code} #{status_message}\r\n" + end + + def mk_headers() = + headers_sent := true + headers = headers() + content_type = content_type() + data = data() + headers = + if + getter.is_constant(data) + then + data = getter.get(data) + if + data != "" + then + ("Content-Length", "#{string.length(data)}")::headers + else + headers + end + else + ("Transfer-Encoding", "chunked")::headers + end + + headers = + if + null.defined(content_type) and null.get(content_type) != "" + then + ("Content-type", null.get(content_type))::headers + else + headers + end + + headers = list.map(fun (v) -> "#{fst(v)}: #{snd(v)}", headers) + headers = string.concat(separator="\r\n", headers) + headers = if headers != "" then "#{headers}\r\n" else "" end + "#{headers}\r\n" + end + + def mk_data() = + data_sent := true + data = data() + if + getter.is_constant(data) + then + response_ended := true + getter.get(data) + else + data = getter.get(data) + response_ended := data == "" + "#{string.hex_of_int(string.length(data))}\r\n#{data}\r\n" + end + end + + def response() = + if + response_ended() + then + "" + elsif not status_sent() then mk_status() + elsif not headers_sent() then mk_headers() + else + mk_data() + end + end + + def attr_method(sent, attr) = + def set(v) = + if + sent() + then + error.raise( + error.invalid, "HTTP response has already been sent for this value!" + ) + end + + attr := v + end + + def get() = + attr() + end + + set.{current=get} + end + + def header(k, v) = + headers := (k, v)::headers() + end + + code = status_code + + def redirect(~status_code=301, location) = + if + status_sent() + then + error.raise( + error.invalid, "HTTP response has already been sent for this value!" + ) + end + + code := status_code + header("Location", location) + end + + def json(~compact=true, v) = + if + headers_sent() + then + error.raise( + error.invalid, "HTTP response has already been sent for this value!" + ) + end + + content_type := "application/json; charset=utf-8" + data := json.stringify(v, compact=compact) ^ "\n" + end + + def html(d) = + if + headers_sent() + then + error.raise( + error.invalid, "HTTP response has already been sent for this value!" + ) + end + + content_type := "text/html" + data := d + end + + def send_status(socket) = + if not status_sent() then socket.write(mk_status()) end + end + + def multipart_form(~boundary=null(), contents) = + if + headers_sent() + then + error.raise( + error.invalid, "HTTP response has already been sent for this value!" + ) + end + + form_data = http.multipart_form_data(boundary=boundary, contents) + content_type := "multipart/form-data; boundary=#{form_data.boundary}" + data := form_data.contents + end + + response.{ + http_version=attr_method(status_sent, http_version), + status_code=attr_method(status_sent, status_code), + status_message=attr_method(status_sent, status_message), + headers=attr_method(headers_sent, headers), + header=header, + redirect=redirect, + json=json, + html=html, + content_type=attr_method(headers_sent, content_type), + multipart_form=multipart_form, + data=attr_method(data_sent, data), + send_status=send_status, + status_sent={status_sent()} + } +end \ No newline at end of file diff --git a/samples/Liquidsoap/playlist.liq b/samples/Liquidsoap/playlist.liq new file mode 100644 index 0000000000..bd86ab58ca --- /dev/null +++ b/samples/Liquidsoap/playlist.liq @@ -0,0 +1,224 @@ +# Play a list of files. +# @category Source / Input +# @param ~id Force the value of the source ID. +# @param ~check_next Function used to filter next tracks. A candidate track is \ +# only validated if the function returns true on it. The function is called \ +# before resolution, hence metadata will only be available for requests \ +# corresponding to local files. This is typically used to avoid repetitions, \ +# but be careful: if the function rejects all attempts, the playlist will \ +# enter into a consuming loop and stop playing anything. +# @param ~prefetch How many requests should be queued in advance. +# @param ~loop Loop on the playlist. +# @param ~mode Play the files in the playlist either in the order ("normal" mode), \ +# or shuffle the playlist each time it is loaded, and play it in this order for a \ +# whole round ("randomize" mode), or pick a random file in the playlist each time \ +# ("random" mode). +# @param ~native Use native implementation, when available. +# @param ~on_loop Function executed when the playlist is about to loop. +# @param ~on_done Function executed when the playlist is finished. +# @param ~max_fail When this number of requests fail to resolve, the whole playlists is considered as failed and `on_fail` is called. +# @param ~on_fail Function executed when too many requests failed and returning the contents of a fixed playlist. +# @param ~timeout Timeout (in sec.) for a single download. +# @param playlist Playlist. +# @method reload Reload the playlist with given list of songs. +# @method remaining_files Songs remaining to be played. +def playlist.list( + ~id=null(), + ~check_next=null(), + ~prefetch=1, + ~loop=true, + ~mode="normal", + ~native=false, + ~on_loop={()}, + ~on_done={()}, + ~max_fail=10, + ~on_fail=null(), + ~timeout=20., + playlist +) = + ignore(native) + id = string.id.default(default="playlist.list", id) + mode = + if + not list.mem(mode, ["normal", "random", "randomize"]) + then + log.severe(label=id, "Invalid mode: #{mode}") + "randomize" + else + mode + end + + check_next = check_next ?? fun (_) -> true + should_stop = ref(false) + on_shutdown({should_stop.set(true)}) + on_fail = + null.map( + fun (on_fail) -> {if not should_stop() then on_fail() else [] end}, + on_fail + ) + + + # Original playlist when loaded + playlist_orig = ref(playlist) + + # Randomize the playlist if necessary + def randomize(p) = + if mode == "randomize" then list.shuffle(p) else p end + end + + # Current remaining playlist + playlist = ref(randomize(playlist)) + + # A reference to know if the source has been stopped + has_stopped = ref(false) + + # Delay the creation of next after the source because we need it to resolve + # requests at the right content type. + next_fun = ref(fun () -> null()) + + def next() = + f = next_fun() + f() + end + + # Instantiate the source + default = + request.dynamic( + id=id, + prefetch=prefetch, + timeout=timeout, + retry_delay=1., + available={not has_stopped()}, + next + ) + + s = +%ifdef native + if native then stdlib_native.request.dynamic(id=id, next) else default end +%else + default +%endif + + source.set_name(s, "playlist.list.reloadable") + + # The reload function + def reload(~empty_queue=true, p) = + log.debug(label=id, "Reloading playlist.") + playlist_orig := p + playlist := randomize(playlist_orig()) + has_stopped := false + if + empty_queue + then + q = s.queue() + s.set_queue([]) + list.iter(request.destroy, q) + ignore(s.fetch()) + end + end + + # When we have more than max_fail failures in a row, we wait for 1 second + # before trying again in order to avoid infinite loops. + failed_count = ref(0) + failed_time = ref(0.) + + # The (real) next function + def rec next() = + if + loop and list.is_empty(playlist()) + then + on_loop() + + # The above function might have reloaded the playlist + if + list.is_empty(playlist()) + then + playlist := randomize(playlist_orig()) + end + end + + file = + if + list.length(playlist()) > 0 + then + if + mode == "random" + then + n = random.int(min=0, max=list.length(playlist())) + list.nth(default="", playlist(), n) + else + ret = list.hd(default="", playlist()) + playlist := list.tl(playlist()) + ret + end + else + # Playlist finished + if + not has_stopped() + then + has_stopped := true + log.info(label=id, "Playlist stopped.") + on_done() + end + + "" + end + + if + file == "" or (failed_count() >= max_fail and time() < failed_time() + 1.) + then + # Playlist failed too many times recently, don't try next for now. + null() + else + log.debug(label=id, "Next song will be \"#{file}\".") + r = request.create(file) + if + not request.resolve(content_type=s, r) + then + log.info(label=id, "Could not resolve request: #{request.uri(r)}.") + request.destroy(r) + ref.incr(failed_count) + + # Playlist failed, call handler. + if + failed_count() < max_fail + then + log.info(label=id, "Playlist failed.") + if + null.defined(on_fail) + then + f = null.get(on_fail) + reload(f()) + end + end + + failed_time := time() + (next() : request?) + else + failed_count := 0 + if + check_next(r) + then + r + else + log.info( + label=id, "Request #{request.uri(r)} rejected by check_next." + ) + + request.destroy(r) + next() + end + end + end + end + + next_fun := next + + # List of songs remaining to be played + def remaining_files() = + playlist() + end + + # Return + s.{reload=reload, remaining_files=remaining_files} +end \ No newline at end of file diff --git a/samples/Liquidsoap/utils.liq b/samples/Liquidsoap/utils.liq new file mode 100644 index 0000000000..d51308b308 --- /dev/null +++ b/samples/Liquidsoap/utils.liq @@ -0,0 +1,67 @@ +%ifdef environment +# Get the value of an environment variable. +# Returns `default` if the variable is not set. +# @category System +def environment.get(~default="", s) = + env = environment() + if + not list.assoc.mem(s, env) + then + default + else + list.assoc(default=default, s, env) + end +end +%endif + +# Split the arguments of an url of the form `arg=bar&arg2=bar2` into +# `[("arg","bar"),("arg2","bar2")]`. The returned strings are decoded (see +# `url.decode`). +# @category String +# @param args Argument string to split. +def url.split_args(args) = + def f(x) = + ret = r/=/.split(x) + arg = url.decode(list.nth(default="", ret, 0)) + val = url.decode(list.nth(default="", ret, 1)) + (arg, val) + end + + l = r/&/.split(args) + list.map(f, l) +end + +# Split an url of the form `foo?arg=bar&arg2=bar2` into +# `("foo",[("arg","bar"),("arg2","bar2")])`. The returned strings are decoded +# (see `url.decode`). +# @category String +# @param uri Url to split. +def url.split(uri) = + ret = r/(?[^\?]*)\?(?.*)/.exec(uri).groups + args = ret["args"] + if + args != "" + then + (url.decode(ret["uri"]), url.split_args(args)) + else + (url.decode(uri), []) + end +end + +# Memoize the result of a function, making sure it is only executed once. +# @category Programming +def memoize(fn) = + cached_result = ref([]) + fun () -> + begin + if + cached_result() != [] + then + list.hd(cached_result()) + else + result = fn() + cached_result := [result] + result + end + end +end diff --git a/vendor/README.md b/vendor/README.md index 67f1d452b0..1001b1507e 100644 --- a/vendor/README.md +++ b/vendor/README.md @@ -305,6 +305,7 @@ This is a list of grammars that Linguist selects to provide syntax highlighting - **LigoLANG:** [pewulfman/Ligo-grammar](https://github.com/pewulfman/Ligo-grammar) - **LilyPond:** [nwhetsell/linter-lilypond](https://github.com/nwhetsell/linter-lilypond) - **Liquid:** [Shopify/liquid-tm-grammar](https://github.com/Shopify/liquid-tm-grammar) +- **Liquidsoap:** [savonet/vscode-liquidsoap](https://github.com/savonet/vscode-liquidsoap) - **Literate CoffeeScript:** [atom/language-coffee-script](https://github.com/atom/language-coffee-script) - **Literate Haskell:** [atom-haskell/language-haskell](https://github.com/atom-haskell/language-haskell) - **LiveScript:** [paulmillr/LiveScript.tmbundle](https://github.com/paulmillr/LiveScript.tmbundle) diff --git a/vendor/grammars/vscode-liquidsoap b/vendor/grammars/vscode-liquidsoap new file mode 160000 index 0000000000..2c1e023669 --- /dev/null +++ b/vendor/grammars/vscode-liquidsoap @@ -0,0 +1 @@ +Subproject commit 2c1e023669c0e45aa84fa31b3b6ea5db2dd97ea1 diff --git a/vendor/licenses/git_submodule/vscode-liquidsoap.dep.yml b/vendor/licenses/git_submodule/vscode-liquidsoap.dep.yml new file mode 100644 index 0000000000..a65e1d626d --- /dev/null +++ b/vendor/licenses/git_submodule/vscode-liquidsoap.dep.yml @@ -0,0 +1,33 @@ +--- +name: vscode-liquidsoap +version: 2c1e023669c0e45aa84fa31b3b6ea5db2dd97ea1 +type: git_submodule +homepage: https://github.com/savonet/vscode-liquidsoap.git +license: mit +licenses: +- sources: LICENSE + text: | + vscode-liquidsoap + + The MIT License (MIT) + + Original Work Copyright (c) 2023 The Savonet Team + + 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. +notices: [] From cc4260a1dbe8f7359483c9b6b4ea7cc6b0ab76c4 Mon Sep 17 00:00:00 2001 From: Romain Beauxis Date: Thu, 12 Sep 2024 12:21:38 -0500 Subject: [PATCH 2/2] Order. --- lib/linguist/languages.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index e876528745..f3af2d2b15 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -3919,14 +3919,6 @@ Linux Kernel Module: tm_scope: none ace_mode: text language_id: 203 -Liquidsoap: - type: programming - color: "#990066" - extensions: - - ".liq" - tm_scope: source.liquidsoap - ace_mode: text - language_id: 614641732 Liquid: type: markup color: "#67b8de" @@ -3935,6 +3927,14 @@ Liquid: tm_scope: text.html.liquid ace_mode: liquid language_id: 204 +Liquidsoap: + type: programming + color: "#990066" + extensions: + - ".liq" + tm_scope: source.liquidsoap + ace_mode: text + language_id: 614641732 Literate Agda: type: programming color: "#315665"