iris is the framework alone; the app is one crate in app-rust/
Iris: "the organization of the rust rewrite is a mess right now... there shouldn't be anything related to the app inside of iris. Iris is supposed to be the UI framework alone." And, on the crate count: "I'm confused why the app only code needs more than one crate though." Nine cargo workspaces become three, and the port's project code -- which sat in five places, four of them inside the framework -- becomes one crate, `ai-app`, in `app-rust/`: client-core -> app-rust/src/client iris/transcript-ui -> app-rust/src/ui iris/transcript-fixture -> app-rust/src/ui/fixture.rs + tests/ + touch/ iris/desktop-app -> app-rust/src/desktop + src/bin_desktop.rs iris/android-app -> app-rust/src/android + android-project/ android-shell -> app-rust/src/shell iris/ keeps core, macro, the iris crate, tabs-ui and rig-input, and now mentions no session, transcript, setup or server anywhere. Only two of the old splits had a reason that survived reading. event-model stays a crate at the repo root because server/ depends on it too, so a crate is what makes the backend and the app agree by construction. The two Android .so names looked like a hard constraint -- a package produces one library artifact -- until P2 turned out to already plan merging those two Android apps into one; both faces now come out of libai_app.so, picked apart by features so `--no-default-features --features shell` keeps wgpu, parley and iris out of the Compose app's APK. docs/RUST.md's "One app crate" has the rest, including what each remaining feature is for. DECISIONS.md and SUBAGENTS.md move into docs/ with everything else. Verified: ./run-tests.sh and `cd iris && cargo test` green, clippy and fmt clean in all five workspaces, `cargo ndk -t x86_64` links libai_app.so, build-apk.sh produces an APK that installs and launches on this checkout's emulator (Gl ... virgl, as expected), and the phone-sized headless screenshot renders the transcript unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e9a6562dc6
commit
6d5a231f5c
100 files changed
+924
-3295
No files matched your search
@@ -0,0 +1,581 @@
|
||||
//! A language the highlighter can colour, and the data-driven [`Rules`] each
|
||||
//! one scans by. Ported from `app/.../Languages.kt`; see that file's doc for
|
||||
//! why nearly every language is a row of data read by one shared scanner,
|
||||
//! with Markdown the one exception (`super::markdown`).
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum Language {
|
||||
C,
|
||||
Coffeescript,
|
||||
Cpp,
|
||||
Csharp,
|
||||
Dart,
|
||||
Fish,
|
||||
Go,
|
||||
Java,
|
||||
Javascript,
|
||||
Json,
|
||||
Kotlin,
|
||||
Markdown,
|
||||
Perl,
|
||||
Php,
|
||||
Python,
|
||||
Ron,
|
||||
Ruby,
|
||||
Rust,
|
||||
Shell,
|
||||
Swift,
|
||||
Toml,
|
||||
Typescript,
|
||||
}
|
||||
|
||||
impl Language {
|
||||
/// Every value, for the same exhaustiveness check the Kotlin test runs
|
||||
/// (`Language.entries`).
|
||||
pub const ALL: [Language; 22] = [
|
||||
Language::C,
|
||||
Language::Coffeescript,
|
||||
Language::Cpp,
|
||||
Language::Csharp,
|
||||
Language::Dart,
|
||||
Language::Fish,
|
||||
Language::Go,
|
||||
Language::Java,
|
||||
Language::Javascript,
|
||||
Language::Json,
|
||||
Language::Kotlin,
|
||||
Language::Markdown,
|
||||
Language::Perl,
|
||||
Language::Php,
|
||||
Language::Python,
|
||||
Language::Ron,
|
||||
Language::Ruby,
|
||||
Language::Rust,
|
||||
Language::Shell,
|
||||
Language::Swift,
|
||||
Language::Toml,
|
||||
Language::Typescript,
|
||||
];
|
||||
}
|
||||
|
||||
/// What [`super::scan`] needs to know about one language -- data, not code,
|
||||
/// so that adding a language is a row here rather than a branch anywhere.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Rules {
|
||||
/// Words drawn as keywords. Only plain words; the scanner cannot reach
|
||||
/// anything else.
|
||||
pub keywords: HashSet<&'static str>,
|
||||
/// Tokens that open a comment running to the end of the line.
|
||||
pub line_comments: Vec<&'static str>,
|
||||
/// Whether `line_comments` count only at the start of a word. The shells
|
||||
/// need it: `$#`, `${#x}` and `a#b` are not comments.
|
||||
pub line_comments_at_word_start: bool,
|
||||
pub block_comment: Option<BlockComment>,
|
||||
/// The string forms. The longest opener that matches wins, so `"""` is
|
||||
/// tried before `"`.
|
||||
pub quotes: Vec<Quote>,
|
||||
pub attributes: Attributes,
|
||||
/// Rust and RON: an optional `b`, `r`, n hashes, `"`, closing at `"` and n hashes.
|
||||
pub raw_strings: bool,
|
||||
/// Rust: `'` opens a character literal only when a backslash or one
|
||||
/// character and a `'` follow. Otherwise it is a lifetime or a label.
|
||||
pub lifetimes: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlockComment {
|
||||
pub open: &'static str,
|
||||
pub close: &'static str,
|
||||
pub nests: bool,
|
||||
}
|
||||
|
||||
/// One string form. `escapes` is whether a backslash escapes the closer
|
||||
/// (and itself).
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Quote {
|
||||
pub open: &'static str,
|
||||
pub close: &'static str,
|
||||
pub escapes: bool,
|
||||
}
|
||||
|
||||
/// What opens a metadata span, of the shapes that exist across these languages.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub enum Attributes {
|
||||
#[default]
|
||||
None,
|
||||
/// `@` and a word: Kotlin and Java annotations, Python decorators.
|
||||
AtWord,
|
||||
/// `#[` or `#![` through the matching `]`: Rust and RON attributes.
|
||||
HashBracket,
|
||||
/// `#` at the start of a line, to the end of it: the C preprocessor.
|
||||
HashLine,
|
||||
/// `[` at the start of a line through the matching `]`: a TOML table header.
|
||||
LineBracket,
|
||||
}
|
||||
|
||||
const C_STYLE: BlockComment = BlockComment {
|
||||
open: "/*",
|
||||
close: "*/",
|
||||
nests: false,
|
||||
};
|
||||
const NESTING: BlockComment = BlockComment {
|
||||
open: "/*",
|
||||
close: "*/",
|
||||
nests: true,
|
||||
};
|
||||
|
||||
const DOUBLE: Quote = Quote {
|
||||
open: "\"",
|
||||
close: "\"",
|
||||
escapes: true,
|
||||
};
|
||||
const SINGLE: Quote = Quote {
|
||||
open: "'",
|
||||
close: "'",
|
||||
escapes: true,
|
||||
};
|
||||
const TRIPLE_DOUBLE: Quote = Quote {
|
||||
open: "\"\"\"",
|
||||
close: "\"\"\"",
|
||||
escapes: true,
|
||||
};
|
||||
const TRIPLE_SINGLE: Quote = Quote {
|
||||
open: "'''",
|
||||
close: "'''",
|
||||
escapes: true,
|
||||
};
|
||||
|
||||
fn words(list: &'static str) -> HashSet<&'static str> {
|
||||
list.split_whitespace().collect()
|
||||
}
|
||||
|
||||
/// The rules for one language. A `match` rather than a lazily-built map --
|
||||
/// there is no once-per-process cost worth paying for in a language table
|
||||
/// this small, and it sidesteps the Kotlin version's own workaround for
|
||||
/// property initialization order.
|
||||
pub fn rules_for(language: Language) -> Rules {
|
||||
match language {
|
||||
Language::C => Rules {
|
||||
keywords: words(KEYWORDS_C),
|
||||
line_comments: vec!["//"],
|
||||
block_comment: Some(C_STYLE),
|
||||
quotes: vec![DOUBLE, SINGLE],
|
||||
attributes: Attributes::HashLine,
|
||||
..Default::default()
|
||||
},
|
||||
Language::Cpp => Rules {
|
||||
keywords: words(KEYWORDS_CPP),
|
||||
line_comments: vec!["//"],
|
||||
block_comment: Some(C_STYLE),
|
||||
quotes: vec![DOUBLE, SINGLE],
|
||||
attributes: Attributes::HashLine,
|
||||
..Default::default()
|
||||
},
|
||||
Language::Csharp => Rules {
|
||||
keywords: words(KEYWORDS_CSHARP),
|
||||
line_comments: vec!["//"],
|
||||
block_comment: Some(C_STYLE),
|
||||
quotes: vec![DOUBLE, SINGLE],
|
||||
..Default::default()
|
||||
},
|
||||
// `###` opens and closes a block comment and `#` opens a line one,
|
||||
// which is why the scanner tries the block opener first.
|
||||
Language::Coffeescript => Rules {
|
||||
keywords: words(KEYWORDS_COFFEESCRIPT),
|
||||
line_comments: vec!["#"],
|
||||
block_comment: Some(BlockComment {
|
||||
open: "###",
|
||||
close: "###",
|
||||
nests: false,
|
||||
}),
|
||||
quotes: vec![TRIPLE_DOUBLE, TRIPLE_SINGLE, DOUBLE, SINGLE],
|
||||
..Default::default()
|
||||
},
|
||||
Language::Dart => Rules {
|
||||
keywords: words(KEYWORDS_DART),
|
||||
line_comments: vec!["//"],
|
||||
block_comment: Some(NESTING),
|
||||
quotes: vec![TRIPLE_DOUBLE, TRIPLE_SINGLE, DOUBLE, SINGLE],
|
||||
attributes: Attributes::AtWord,
|
||||
..Default::default()
|
||||
},
|
||||
Language::Fish => Rules {
|
||||
keywords: words(KEYWORDS_FISH),
|
||||
line_comments: vec!["#"],
|
||||
line_comments_at_word_start: true,
|
||||
quotes: vec![DOUBLE, SINGLE],
|
||||
..Default::default()
|
||||
},
|
||||
Language::Go => Rules {
|
||||
keywords: words(KEYWORDS_GO),
|
||||
line_comments: vec!["//"],
|
||||
block_comment: Some(C_STYLE),
|
||||
quotes: vec![
|
||||
DOUBLE,
|
||||
SINGLE,
|
||||
Quote {
|
||||
open: "`",
|
||||
close: "`",
|
||||
escapes: false,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
Language::Java => Rules {
|
||||
keywords: words(KEYWORDS_JAVA),
|
||||
line_comments: vec!["//"],
|
||||
block_comment: Some(C_STYLE),
|
||||
quotes: vec![DOUBLE, SINGLE],
|
||||
attributes: Attributes::AtWord,
|
||||
..Default::default()
|
||||
},
|
||||
Language::Javascript => Rules {
|
||||
keywords: words(KEYWORDS_JAVASCRIPT),
|
||||
line_comments: vec!["//"],
|
||||
block_comment: Some(C_STYLE),
|
||||
quotes: vec![
|
||||
DOUBLE,
|
||||
SINGLE,
|
||||
Quote {
|
||||
open: "`",
|
||||
close: "`",
|
||||
escapes: true,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
Language::Json => Rules {
|
||||
keywords: words(KEYWORDS_JSON),
|
||||
quotes: vec![DOUBLE],
|
||||
..Default::default()
|
||||
},
|
||||
Language::Kotlin => Rules {
|
||||
keywords: words(KEYWORDS_KOTLIN),
|
||||
line_comments: vec!["//"],
|
||||
block_comment: Some(NESTING),
|
||||
quotes: vec![
|
||||
Quote {
|
||||
open: "\"\"\"",
|
||||
close: "\"\"\"",
|
||||
escapes: false,
|
||||
},
|
||||
DOUBLE,
|
||||
SINGLE,
|
||||
],
|
||||
attributes: Attributes::AtWord,
|
||||
..Default::default()
|
||||
},
|
||||
Language::Perl => Rules {
|
||||
keywords: words(KEYWORDS_PERL),
|
||||
line_comments: vec!["#"],
|
||||
quotes: vec![DOUBLE, SINGLE],
|
||||
..Default::default()
|
||||
},
|
||||
Language::Php => Rules {
|
||||
keywords: words(KEYWORDS_PHP),
|
||||
line_comments: vec!["//", "#"],
|
||||
block_comment: Some(C_STYLE),
|
||||
quotes: vec![DOUBLE, SINGLE],
|
||||
attributes: Attributes::AtWord,
|
||||
..Default::default()
|
||||
},
|
||||
Language::Python => Rules {
|
||||
keywords: words(KEYWORDS_PYTHON),
|
||||
line_comments: vec!["#"],
|
||||
quotes: vec![TRIPLE_DOUBLE, TRIPLE_SINGLE, DOUBLE, SINGLE],
|
||||
attributes: Attributes::AtWord,
|
||||
..Default::default()
|
||||
},
|
||||
Language::Ron => Rules {
|
||||
keywords: words(KEYWORDS_RON),
|
||||
line_comments: vec!["//"],
|
||||
block_comment: Some(NESTING),
|
||||
quotes: vec![DOUBLE, SINGLE],
|
||||
attributes: Attributes::HashBracket,
|
||||
raw_strings: true,
|
||||
..Default::default()
|
||||
},
|
||||
Language::Ruby => Rules {
|
||||
keywords: words(KEYWORDS_RUBY),
|
||||
line_comments: vec!["#"],
|
||||
quotes: vec![DOUBLE, SINGLE],
|
||||
..Default::default()
|
||||
},
|
||||
Language::Rust => Rules {
|
||||
keywords: words(KEYWORDS_RUST),
|
||||
line_comments: vec!["//"],
|
||||
block_comment: Some(NESTING),
|
||||
// No `'` here: `lifetimes` decides when one opens a character literal.
|
||||
quotes: vec![DOUBLE],
|
||||
attributes: Attributes::HashBracket,
|
||||
raw_strings: true,
|
||||
lifetimes: true,
|
||||
..Default::default()
|
||||
},
|
||||
Language::Shell => Rules {
|
||||
keywords: words(KEYWORDS_SHELL),
|
||||
line_comments: vec!["#"],
|
||||
line_comments_at_word_start: true,
|
||||
// A shell's single quotes are literal: `'a\'` is not one string.
|
||||
quotes: vec![
|
||||
DOUBLE,
|
||||
Quote {
|
||||
open: "'",
|
||||
close: "'",
|
||||
escapes: false,
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
Language::Swift => Rules {
|
||||
keywords: words(KEYWORDS_SWIFT),
|
||||
line_comments: vec!["//"],
|
||||
block_comment: Some(NESTING),
|
||||
quotes: vec![TRIPLE_DOUBLE, DOUBLE],
|
||||
attributes: Attributes::AtWord,
|
||||
..Default::default()
|
||||
},
|
||||
Language::Toml => Rules {
|
||||
keywords: words(KEYWORDS_TOML),
|
||||
line_comments: vec!["#"],
|
||||
quotes: vec![
|
||||
TRIPLE_DOUBLE,
|
||||
Quote {
|
||||
open: "'''",
|
||||
close: "'''",
|
||||
escapes: false,
|
||||
},
|
||||
DOUBLE,
|
||||
Quote {
|
||||
open: "'",
|
||||
close: "'",
|
||||
escapes: false,
|
||||
},
|
||||
],
|
||||
attributes: Attributes::LineBracket,
|
||||
..Default::default()
|
||||
},
|
||||
Language::Typescript => Rules {
|
||||
keywords: words(KEYWORDS_TYPESCRIPT),
|
||||
line_comments: vec!["//"],
|
||||
block_comment: Some(C_STYLE),
|
||||
quotes: vec![
|
||||
DOUBLE,
|
||||
SINGLE,
|
||||
Quote {
|
||||
open: "`",
|
||||
close: "`",
|
||||
escapes: true,
|
||||
},
|
||||
],
|
||||
attributes: Attributes::AtWord,
|
||||
..Default::default()
|
||||
},
|
||||
// Markdown has no token rules; see `super::markdown::scan_markdown`.
|
||||
Language::Markdown => Rules::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// The keyword sets. Every list below other than RON, TOML, fish and JSON
|
||||
// came from dev.snipme:highlights 1.1.0 (Apache-2.0), the library the
|
||||
// Kotlin scanner replaced, so that no fence which was coloured there turns
|
||||
// plain here either.
|
||||
|
||||
const KEYWORDS_C: &str =
|
||||
"auto break case char const continue default do double else enum extern float for goto if
|
||||
int long register return short signed sizeof static struct switch typedef union unsigned
|
||||
void volatile while";
|
||||
|
||||
const KEYWORDS_CPP: &str =
|
||||
"asm auto bool break case catch char class const const_cast continue default delete do
|
||||
double dynamic_cast else enum explicit export extern false float for friend goto if inline
|
||||
int long mutable namespace new operator private protected public register reinterpret_cast
|
||||
return short signed sizeof static static_cast struct switch template this throw true try
|
||||
typedef typeid typename union unsigned using virtual void volatile wchar_t while";
|
||||
|
||||
const KEYWORDS_CSHARP: &str =
|
||||
"abstract as base bool break byte case catch char checked class const continue decimal
|
||||
default delegate do double else enum event explicit extern false finally fixed float for
|
||||
foreach goto if implicit in int interface internal is lock long namespace new null object
|
||||
operator out override params private protected public readonly ref return sbyte sealed short
|
||||
sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked
|
||||
unsafe ushort using virtual void volatile while";
|
||||
|
||||
const KEYWORDS_COFFEESCRIPT: &str =
|
||||
"Infinity NaN and arguments await break by case catch class continue debugger delete defer
|
||||
default do else export extends false finally for function if import in instanceof is isnt
|
||||
let loop new no not null of on or package return super switch this throw true try typeof
|
||||
unless undefined var wait when with yield";
|
||||
|
||||
const KEYWORDS_DART: &str =
|
||||
"abstract as assert async await base break case catch class const continue covariant
|
||||
default deferred do dynamic else enum export extends external factory false final finally
|
||||
for get if implements import in interface is late library mixin new null on operator part
|
||||
required rethrow return sealed set show static super switch this throw true try var void
|
||||
when with while yield";
|
||||
|
||||
/// fish is not in the library at all, so its fences are drawn plain today.
|
||||
/// The list is the shell's own words, which is what a fish fence is mostly
|
||||
/// made of.
|
||||
const KEYWORDS_FISH: &str =
|
||||
"and begin break builtin case command continue else end exec for function if in not or
|
||||
return switch while set echo test string math read source";
|
||||
|
||||
const KEYWORDS_GO: &str =
|
||||
"break case chan const continue default defer else fallthrough false for func go goto if
|
||||
import interface map package range return select struct switch true type var";
|
||||
|
||||
const KEYWORDS_JAVA: &str =
|
||||
"abstract assert boolean break byte case catch char class const continue default do double
|
||||
else enum extends final finally float for goto if implements import instanceof int interface
|
||||
long native new null package private protected public return short static strictfp super
|
||||
switch synchronized this throw throws transient try void volatile while";
|
||||
|
||||
const KEYWORDS_JAVASCRIPT: &str =
|
||||
"async await boolean break case catch class const continue debugger default delete do else
|
||||
enum export extends false finally for function if implements import in instanceof interface
|
||||
let new null package private protected public return super switch this throw true try typeof
|
||||
var void while with yield";
|
||||
|
||||
const KEYWORDS_JSON: &str = "true false null";
|
||||
|
||||
const KEYWORDS_KOTLIN: &str =
|
||||
"actual abstract annotation as break by catch class companion const constructor continue
|
||||
coroutine crossinline data delegate dynamic do else enum expect external false final finally
|
||||
for fun get if import in infix inline interface internal is lazy lateinit native null object
|
||||
open operator out override package private protected public reified return sealed set super
|
||||
suspend tailrec this throw true try typealias typeof val var vararg when while yield";
|
||||
|
||||
const KEYWORDS_PERL: &str =
|
||||
"__DATA__ __END__ __FILE__ __LINE__ __PACKAGE__ and cmp continue do else elsif eq eval for
|
||||
foreach goto gt if last le lt my ne next no not or package redo ref return sub unless until
|
||||
use while xor";
|
||||
|
||||
const KEYWORDS_PHP: &str =
|
||||
"__halt_compiler abstract and array as break callable case catch class clone const continue
|
||||
declare default die do echo else elseif empty enddeclare endfor endforeach endif endswitch
|
||||
endwhile eval exit extends final finally fn for foreach function global goto if implements
|
||||
include include_once instanceof insteadof interface isset list match new or print private
|
||||
protected public require require_once return static switch throw trait try unset use var
|
||||
while xor yield";
|
||||
|
||||
const KEYWORDS_PYTHON: &str =
|
||||
"False True and as assert async await break class continue def del elif else except finally
|
||||
for from global if import in is lambda nonlocal not or pass raise return try while with
|
||||
yield";
|
||||
|
||||
/// RON is not in the library either; these are the words a RON file can hold.
|
||||
const KEYWORDS_RON: &str = "true false Some None inf NaN";
|
||||
|
||||
const KEYWORDS_RUBY: &str =
|
||||
"__ENCODING__ __END__ __FILE__ __LINE__ BEGIN END alias and begin break case class def do
|
||||
else elsif end ensure false for if in module next nil not or redo rescue retry return self
|
||||
super then true undef unless until when while yield";
|
||||
|
||||
const KEYWORDS_RUST: &str =
|
||||
"as async await break const continue crate dyn else enum extern false fn for if impl in
|
||||
let loop match mod move mut pub ref return Self self static struct super trait true type
|
||||
union unsafe use where while abstract become box do final macro override priv try typeof
|
||||
unsized virtual yield";
|
||||
|
||||
const KEYWORDS_SHELL: &str =
|
||||
"alias bg bind break builtin caller cd command compgen complete compopt continue declare
|
||||
dirs disown echo enable eval exec exit export fc fg getopts hash help history jobs kill let
|
||||
local logout popd printf pushd pwd read readonly return set shift shopt source suspend
|
||||
test";
|
||||
|
||||
const KEYWORDS_SWIFT: &str =
|
||||
"_ associatedtype class deinit enum extension fileprivate func import init inout internal
|
||||
let open operator private precedencegroup protocol public rethrows static struct subscript
|
||||
typealias var break case catch continue default defer do else fallthrough for guard if in
|
||||
repeat return throw switch where while Any as await false is nil self Self super throws true
|
||||
try associativity convenience didSet dynamic final get indirect infix lazy left mutating none
|
||||
nonmutating optional override postfix precedence prefix Protocol required right set some Type
|
||||
unowned weak willSet";
|
||||
|
||||
/// TOML is not in the library; `inf` and `nan` are values rather than
|
||||
/// names, like the booleans.
|
||||
const KEYWORDS_TOML: &str = "true false inf nan";
|
||||
|
||||
const KEYWORDS_TYPESCRIPT: &str =
|
||||
"abstract as asserts await break case catch class const constructor continue debugger
|
||||
default delete do else enum export extends false finally for from function get if implements
|
||||
import in infer instanceof interface is keyof let module namespace new null number object
|
||||
package private protected public readonly require global return set static string super
|
||||
switch this throw true try type typeof undefined unique unknown var void while with yield";
|
||||
|
||||
/// The highlighter's language for a fence's info word, or `None` for one it
|
||||
/// has no rules for. Also what `super::file_language` reads for a file's
|
||||
/// extension -- one table, so a language added for fences is a language
|
||||
/// added for files.
|
||||
pub fn fence_language(name: Option<&str>) -> Option<Language> {
|
||||
let name = name?.trim().to_lowercase();
|
||||
FENCE_LANGUAGES
|
||||
.iter()
|
||||
.find(|(alias, _)| *alias == name)
|
||||
.map(|(_, language)| *language)
|
||||
}
|
||||
|
||||
/// The highlighter's language for a *file*, from its name.
|
||||
///
|
||||
/// The extension is the part after the *last* dot, which is what makes
|
||||
/// `build.gradle.kts` Kotlin. A leading dot is not one: `.bashrc` has no
|
||||
/// extension, it has a name that starts with a dot. A name with no dot at
|
||||
/// all -- `Makefile` -- is likewise `None`.
|
||||
pub fn file_language(name: &str) -> Option<Language> {
|
||||
let dot = name.rfind('.')?;
|
||||
if dot < 1 {
|
||||
return None;
|
||||
}
|
||||
fence_language(Some(&name[dot + 1..]))
|
||||
}
|
||||
|
||||
const FENCE_LANGUAGES: &[(&str, Language)] = &[
|
||||
("kotlin", Language::Kotlin),
|
||||
("kt", Language::Kotlin),
|
||||
("kts", Language::Kotlin),
|
||||
("rust", Language::Rust),
|
||||
("rs", Language::Rust),
|
||||
("sh", Language::Shell),
|
||||
("bash", Language::Shell),
|
||||
("shell", Language::Shell),
|
||||
("zsh", Language::Shell),
|
||||
("console", Language::Shell),
|
||||
("python", Language::Python),
|
||||
("py", Language::Python),
|
||||
("javascript", Language::Javascript),
|
||||
("js", Language::Javascript),
|
||||
("jsx", Language::Javascript),
|
||||
("typescript", Language::Typescript),
|
||||
("ts", Language::Typescript),
|
||||
("tsx", Language::Typescript),
|
||||
("java", Language::Java),
|
||||
("c", Language::C),
|
||||
("h", Language::C),
|
||||
("cpp", Language::Cpp),
|
||||
("c++", Language::Cpp),
|
||||
("cc", Language::Cpp),
|
||||
("hpp", Language::Cpp),
|
||||
("csharp", Language::Csharp),
|
||||
("cs", Language::Csharp),
|
||||
("c#", Language::Csharp),
|
||||
("go", Language::Go),
|
||||
("golang", Language::Go),
|
||||
("swift", Language::Swift),
|
||||
("dart", Language::Dart),
|
||||
("ruby", Language::Ruby),
|
||||
("rb", Language::Ruby),
|
||||
("php", Language::Php),
|
||||
("perl", Language::Perl),
|
||||
("pl", Language::Perl),
|
||||
("coffeescript", Language::Coffeescript),
|
||||
("coffee", Language::Coffeescript),
|
||||
("ron", Language::Ron),
|
||||
("toml", Language::Toml),
|
||||
("fish", Language::Fish),
|
||||
("json", Language::Json),
|
||||
("markdown", Language::Markdown),
|
||||
("md", Language::Markdown),
|
||||
];
|
||||
Reference in new issue
Block a user