mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 14:21:16 +00:00
LSP: color providers
This commit is contained in:
parent
63844a5907
commit
59654472d2
5 changed files with 259 additions and 188 deletions
|
@ -34,6 +34,7 @@ pub mod generator;
|
||||||
pub mod langtype;
|
pub mod langtype;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
|
pub mod literals;
|
||||||
pub(crate) mod load_builtins;
|
pub(crate) mod load_builtins;
|
||||||
pub mod lookup;
|
pub mod lookup;
|
||||||
pub mod namedreference;
|
pub mod namedreference;
|
||||||
|
|
168
sixtyfps_compiler/literals.rs
Normal file
168
sixtyfps_compiler/literals.rs
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
/* LICENSE BEGIN
|
||||||
|
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
||||||
|
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
||||||
|
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
This file is also available under commercial licensing terms.
|
||||||
|
Please contact info@sixtyfps.io for more information.
|
||||||
|
LICENSE END */
|
||||||
|
|
||||||
|
use crate::expression_tree::Expression;
|
||||||
|
|
||||||
|
/// Returns `0xaarrggbb`
|
||||||
|
pub fn parse_color_literal(str: &str) -> Option<u32> {
|
||||||
|
if !str.starts_with('#') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if !str.is_ascii() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let str = &str[1..];
|
||||||
|
let (r, g, b, a) = match str.len() {
|
||||||
|
3 => (
|
||||||
|
u8::from_str_radix(&str[0..=0], 16).ok()? * 0x11,
|
||||||
|
u8::from_str_radix(&str[1..=1], 16).ok()? * 0x11,
|
||||||
|
u8::from_str_radix(&str[2..=2], 16).ok()? * 0x11,
|
||||||
|
255u8,
|
||||||
|
),
|
||||||
|
4 => (
|
||||||
|
u8::from_str_radix(&str[0..=0], 16).ok()? * 0x11,
|
||||||
|
u8::from_str_radix(&str[1..=1], 16).ok()? * 0x11,
|
||||||
|
u8::from_str_radix(&str[2..=2], 16).ok()? * 0x11,
|
||||||
|
u8::from_str_radix(&str[3..=3], 16).ok()? * 0x11,
|
||||||
|
),
|
||||||
|
6 => (
|
||||||
|
u8::from_str_radix(&str[0..2], 16).ok()?,
|
||||||
|
u8::from_str_radix(&str[2..4], 16).ok()?,
|
||||||
|
u8::from_str_radix(&str[4..6], 16).ok()?,
|
||||||
|
255u8,
|
||||||
|
),
|
||||||
|
8 => (
|
||||||
|
u8::from_str_radix(&str[0..2], 16).ok()?,
|
||||||
|
u8::from_str_radix(&str[2..4], 16).ok()?,
|
||||||
|
u8::from_str_radix(&str[4..6], 16).ok()?,
|
||||||
|
u8::from_str_radix(&str[6..8], 16).ok()?,
|
||||||
|
),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
Some((a as u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | (b as u32))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_color_literal() {
|
||||||
|
assert_eq!(parse_color_literal("#abc"), Some(0xffaabbcc));
|
||||||
|
assert_eq!(parse_color_literal("#ABC"), Some(0xffaabbcc));
|
||||||
|
assert_eq!(parse_color_literal("#AbC"), Some(0xffaabbcc));
|
||||||
|
assert_eq!(parse_color_literal("#AbCd"), Some(0xddaabbcc));
|
||||||
|
assert_eq!(parse_color_literal("#01234567"), Some(0x67012345));
|
||||||
|
assert_eq!(parse_color_literal("#012345"), Some(0xff012345));
|
||||||
|
assert_eq!(parse_color_literal("_01234567"), None);
|
||||||
|
assert_eq!(parse_color_literal("→↓←"), None);
|
||||||
|
assert_eq!(parse_color_literal("#→↓←"), None);
|
||||||
|
assert_eq!(parse_color_literal("#1234567890"), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unescape_string(string: &str) -> Option<String> {
|
||||||
|
if string.contains('\n') {
|
||||||
|
// FIXME: new line in string literal not yet supported
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let string = string.strip_prefix('"').or_else(|| string.strip_prefix('}'))?;
|
||||||
|
let string = string.strip_suffix('"').or_else(|| string.strip_suffix("\\{"))?;
|
||||||
|
if !string.contains('\\') {
|
||||||
|
return Some(string.into());
|
||||||
|
}
|
||||||
|
let mut result = String::with_capacity(string.len());
|
||||||
|
let mut pos = 0;
|
||||||
|
loop {
|
||||||
|
let stop = match string[pos..].find('\\') {
|
||||||
|
Some(stop) => pos + stop,
|
||||||
|
None => {
|
||||||
|
result += &string[pos..];
|
||||||
|
return Some(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if stop + 1 >= string.len() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
result += &string[pos..stop];
|
||||||
|
pos = stop + 2;
|
||||||
|
match string.as_bytes()[stop + 1] {
|
||||||
|
b'"' => result += "\"",
|
||||||
|
b'\\' => result += "\\",
|
||||||
|
b'n' => result += "\n",
|
||||||
|
b'u' => {
|
||||||
|
if string.as_bytes().get(pos)? != &b'{' {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let end = string[pos..].find('}')? + pos;
|
||||||
|
let x = u32::from_str_radix(&string[pos + 1..end], 16).ok()?;
|
||||||
|
result.push(std::char::from_u32(x)?);
|
||||||
|
pos = end + 1;
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unsecape_string() {
|
||||||
|
assert_eq!(unescape_string(r#""foo_bar""#), Some("foo_bar".into()));
|
||||||
|
assert_eq!(unescape_string(r#""foo\"bar""#), Some("foo\"bar".into()));
|
||||||
|
assert_eq!(unescape_string(r#""foo\\\"bar""#), Some("foo\\\"bar".into()));
|
||||||
|
assert_eq!(unescape_string(r#""fo\na\\r""#), Some("fo\na\\r".into()));
|
||||||
|
assert_eq!(unescape_string(r#""fo\xa""#), None);
|
||||||
|
assert_eq!(unescape_string(r#""fooo\""#), None);
|
||||||
|
assert_eq!(unescape_string(r#""f\n\n\nf""#), Some("f\n\n\nf".into()));
|
||||||
|
assert_eq!(unescape_string(r#""music\♪xx""#), None);
|
||||||
|
assert_eq!(unescape_string(r#""music\"♪\"🎝""#), Some("music\"♪\"🎝".into()));
|
||||||
|
assert_eq!(unescape_string(r#""foo_bar"#), None);
|
||||||
|
assert_eq!(unescape_string(r#""foo_bar\"#), None);
|
||||||
|
assert_eq!(unescape_string(r#"foo_bar""#), None);
|
||||||
|
assert_eq!(unescape_string(r#""d\u{8}a\u{d4}f\u{Ed3}""#), Some("d\u{8}a\u{d4}f\u{ED3}".into()));
|
||||||
|
assert_eq!(unescape_string(r#""xxx\""#), None);
|
||||||
|
assert_eq!(unescape_string(r#""xxx\u""#), None);
|
||||||
|
assert_eq!(unescape_string(r#""xxx\uxx""#), None);
|
||||||
|
assert_eq!(unescape_string(r#""xxx\u{""#), None);
|
||||||
|
assert_eq!(unescape_string(r#""xxx\u{22""#), None);
|
||||||
|
assert_eq!(unescape_string(r#""xxx\u{qsdf}""#), None);
|
||||||
|
assert_eq!(unescape_string(r#""xxx\u{1234567890}""#), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_number_literal(s: String) -> Result<Expression, String> {
|
||||||
|
let bytes = s.as_bytes();
|
||||||
|
let mut end = 0;
|
||||||
|
while end < bytes.len() && matches!(bytes[end], b'0'..=b'9' | b'.') {
|
||||||
|
end += 1;
|
||||||
|
}
|
||||||
|
let val = s[..end].parse().map_err(|_| "Cannot parse number literal".to_owned())?;
|
||||||
|
let unit = s[end..].parse().map_err(|_| "Invalid unit".to_owned())?;
|
||||||
|
Ok(Expression::NumberLiteral(val, unit))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_number_literal() {
|
||||||
|
fn doit(s: &str) -> Result<(f64, Unit), String> {
|
||||||
|
parse_number_literal(s.into()).map(|e| match e {
|
||||||
|
Expression::NumberLiteral(a, b) => (a, b),
|
||||||
|
_ => panic!(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(doit("10"), Ok((10., Unit::None)));
|
||||||
|
assert_eq!(doit("10phx"), Ok((10., Unit::Phx)));
|
||||||
|
assert_eq!(doit("10.0phx"), Ok((10., Unit::Phx)));
|
||||||
|
assert_eq!(doit("10.0"), Ok((10., Unit::None)));
|
||||||
|
assert_eq!(doit("1.1phx"), Ok((1.1, Unit::Phx)));
|
||||||
|
assert_eq!(doit("10.10"), Ok((10.10, Unit::None)));
|
||||||
|
assert_eq!(doit("10000000"), Ok((10000000., Unit::None)));
|
||||||
|
assert_eq!(doit("10000001phx"), Ok((10000001., Unit::Phx)));
|
||||||
|
|
||||||
|
let wrong_unit = Err("Invalid unit".to_owned());
|
||||||
|
let cannot_parse = Err("Cannot parse number literal".to_owned());
|
||||||
|
assert_eq!(doit("10000001 phx"), wrong_unit);
|
||||||
|
assert_eq!(doit("12.10.12phx"), cannot_parse);
|
||||||
|
assert_eq!(doit("12.12oo"), wrong_unit);
|
||||||
|
assert_eq!(doit("12.12€"), wrong_unit);
|
||||||
|
}
|
|
@ -250,15 +250,17 @@ impl Expression {
|
||||||
.or_else(|| node.QualifiedName().map(|s| Self::from_qualified_name_node(s.into(), ctx)))
|
.or_else(|| node.QualifiedName().map(|s| Self::from_qualified_name_node(s.into(), ctx)))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
node.child_text(SyntaxKind::StringLiteral).map(|s| {
|
node.child_text(SyntaxKind::StringLiteral).map(|s| {
|
||||||
unescape_string(&s).map(Self::StringLiteral).unwrap_or_else(|| {
|
crate::literals::unescape_string(&s).map(Self::StringLiteral).unwrap_or_else(
|
||||||
|
|| {
|
||||||
ctx.diag.push_error("Cannot parse string literal".into(), &node);
|
ctx.diag.push_error("Cannot parse string literal".into(), &node);
|
||||||
Self::Invalid
|
Self::Invalid
|
||||||
})
|
},
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
node.child_text(SyntaxKind::NumberLiteral)
|
node.child_text(SyntaxKind::NumberLiteral)
|
||||||
.map(parse_number_literal)
|
.map(crate::literals::parse_number_literal)
|
||||||
.transpose()
|
.transpose()
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(|e| {
|
||||||
ctx.diag.push_error(e, &node);
|
ctx.diag.push_error(e, &node);
|
||||||
|
@ -267,7 +269,7 @@ impl Expression {
|
||||||
})
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
node.child_text(SyntaxKind::ColorLiteral).map(|s| {
|
node.child_text(SyntaxKind::ColorLiteral).map(|s| {
|
||||||
parse_color_literal(&s)
|
crate::literals::parse_color_literal(&s)
|
||||||
.map(|i| Expression::Cast {
|
.map(|i| Expression::Cast {
|
||||||
from: Box::new(Expression::NumberLiteral(i as _, Unit::None)),
|
from: Box::new(Expression::NumberLiteral(i as _, Unit::None)),
|
||||||
to: Type::Color,
|
to: Type::Color,
|
||||||
|
@ -297,7 +299,10 @@ impl Expression {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_at_image_url_node(node: syntax_nodes::AtImageUrl, ctx: &mut LookupCtx) -> Self {
|
fn from_at_image_url_node(node: syntax_nodes::AtImageUrl, ctx: &mut LookupCtx) -> Self {
|
||||||
let s = match node.child_text(SyntaxKind::StringLiteral).and_then(|x| unescape_string(&x)) {
|
let s = match node
|
||||||
|
.child_text(SyntaxKind::StringLiteral)
|
||||||
|
.and_then(|x| crate::literals::unescape_string(&x))
|
||||||
|
{
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => {
|
None => {
|
||||||
ctx.diag.push_error("Cannot parse string literal".into(), &node);
|
ctx.diag.push_error("Cannot parse string literal".into(), &node);
|
||||||
|
@ -1140,159 +1145,3 @@ fn maybe_lookup_object(
|
||||||
}
|
}
|
||||||
base
|
base
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_color_literal(str: &str) -> Option<u32> {
|
|
||||||
if !str.starts_with('#') {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if !str.is_ascii() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let str = &str[1..];
|
|
||||||
let (r, g, b, a) = match str.len() {
|
|
||||||
3 => (
|
|
||||||
u8::from_str_radix(&str[0..=0], 16).ok()? * 0x11,
|
|
||||||
u8::from_str_radix(&str[1..=1], 16).ok()? * 0x11,
|
|
||||||
u8::from_str_radix(&str[2..=2], 16).ok()? * 0x11,
|
|
||||||
255u8,
|
|
||||||
),
|
|
||||||
4 => (
|
|
||||||
u8::from_str_radix(&str[0..=0], 16).ok()? * 0x11,
|
|
||||||
u8::from_str_radix(&str[1..=1], 16).ok()? * 0x11,
|
|
||||||
u8::from_str_radix(&str[2..=2], 16).ok()? * 0x11,
|
|
||||||
u8::from_str_radix(&str[3..=3], 16).ok()? * 0x11,
|
|
||||||
),
|
|
||||||
6 => (
|
|
||||||
u8::from_str_radix(&str[0..2], 16).ok()?,
|
|
||||||
u8::from_str_radix(&str[2..4], 16).ok()?,
|
|
||||||
u8::from_str_radix(&str[4..6], 16).ok()?,
|
|
||||||
255u8,
|
|
||||||
),
|
|
||||||
8 => (
|
|
||||||
u8::from_str_radix(&str[0..2], 16).ok()?,
|
|
||||||
u8::from_str_radix(&str[2..4], 16).ok()?,
|
|
||||||
u8::from_str_radix(&str[4..6], 16).ok()?,
|
|
||||||
u8::from_str_radix(&str[6..8], 16).ok()?,
|
|
||||||
),
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
Some((a as u32) << 24 | (r as u32) << 16 | (g as u32) << 8 | (b as u32))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_color_literal() {
|
|
||||||
assert_eq!(parse_color_literal("#abc"), Some(0xffaabbcc));
|
|
||||||
assert_eq!(parse_color_literal("#ABC"), Some(0xffaabbcc));
|
|
||||||
assert_eq!(parse_color_literal("#AbC"), Some(0xffaabbcc));
|
|
||||||
assert_eq!(parse_color_literal("#AbCd"), Some(0xddaabbcc));
|
|
||||||
assert_eq!(parse_color_literal("#01234567"), Some(0x67012345));
|
|
||||||
assert_eq!(parse_color_literal("#012345"), Some(0xff012345));
|
|
||||||
assert_eq!(parse_color_literal("_01234567"), None);
|
|
||||||
assert_eq!(parse_color_literal("→↓←"), None);
|
|
||||||
assert_eq!(parse_color_literal("#→↓←"), None);
|
|
||||||
assert_eq!(parse_color_literal("#1234567890"), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unescape_string(string: &str) -> Option<String> {
|
|
||||||
if string.contains('\n') {
|
|
||||||
// FIXME: new line in string literal not yet supported
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let string = string.strip_prefix('"').or_else(|| string.strip_prefix('}'))?;
|
|
||||||
let string = string.strip_suffix('"').or_else(|| string.strip_suffix("\\{"))?;
|
|
||||||
if !string.contains('\\') {
|
|
||||||
return Some(string.into());
|
|
||||||
}
|
|
||||||
let mut result = String::with_capacity(string.len());
|
|
||||||
let mut pos = 0;
|
|
||||||
loop {
|
|
||||||
let stop = match string[pos..].find('\\') {
|
|
||||||
Some(stop) => pos + stop,
|
|
||||||
None => {
|
|
||||||
result += &string[pos..];
|
|
||||||
return Some(result);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if stop + 1 >= string.len() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
result += &string[pos..stop];
|
|
||||||
pos = stop + 2;
|
|
||||||
match string.as_bytes()[stop + 1] {
|
|
||||||
b'"' => result += "\"",
|
|
||||||
b'\\' => result += "\\",
|
|
||||||
b'n' => result += "\n",
|
|
||||||
b'u' => {
|
|
||||||
if string.as_bytes().get(pos)? != &b'{' {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let end = string[pos..].find('}')? + pos;
|
|
||||||
let x = u32::from_str_radix(&string[pos + 1..end], 16).ok()?;
|
|
||||||
result.push(std::char::from_u32(x)?);
|
|
||||||
pos = end + 1;
|
|
||||||
}
|
|
||||||
_ => return None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_unsecape_string() {
|
|
||||||
assert_eq!(unescape_string(r#""foo_bar""#), Some("foo_bar".into()));
|
|
||||||
assert_eq!(unescape_string(r#""foo\"bar""#), Some("foo\"bar".into()));
|
|
||||||
assert_eq!(unescape_string(r#""foo\\\"bar""#), Some("foo\\\"bar".into()));
|
|
||||||
assert_eq!(unescape_string(r#""fo\na\\r""#), Some("fo\na\\r".into()));
|
|
||||||
assert_eq!(unescape_string(r#""fo\xa""#), None);
|
|
||||||
assert_eq!(unescape_string(r#""fooo\""#), None);
|
|
||||||
assert_eq!(unescape_string(r#""f\n\n\nf""#), Some("f\n\n\nf".into()));
|
|
||||||
assert_eq!(unescape_string(r#""music\♪xx""#), None);
|
|
||||||
assert_eq!(unescape_string(r#""music\"♪\"🎝""#), Some("music\"♪\"🎝".into()));
|
|
||||||
assert_eq!(unescape_string(r#""foo_bar"#), None);
|
|
||||||
assert_eq!(unescape_string(r#""foo_bar\"#), None);
|
|
||||||
assert_eq!(unescape_string(r#"foo_bar""#), None);
|
|
||||||
assert_eq!(unescape_string(r#""d\u{8}a\u{d4}f\u{Ed3}""#), Some("d\u{8}a\u{d4}f\u{ED3}".into()));
|
|
||||||
assert_eq!(unescape_string(r#""xxx\""#), None);
|
|
||||||
assert_eq!(unescape_string(r#""xxx\u""#), None);
|
|
||||||
assert_eq!(unescape_string(r#""xxx\uxx""#), None);
|
|
||||||
assert_eq!(unescape_string(r#""xxx\u{""#), None);
|
|
||||||
assert_eq!(unescape_string(r#""xxx\u{22""#), None);
|
|
||||||
assert_eq!(unescape_string(r#""xxx\u{qsdf}""#), None);
|
|
||||||
assert_eq!(unescape_string(r#""xxx\u{1234567890}""#), None);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_number_literal(s: String) -> Result<Expression, String> {
|
|
||||||
let bytes = s.as_bytes();
|
|
||||||
let mut end = 0;
|
|
||||||
while end < bytes.len() && matches!(bytes[end], b'0'..=b'9' | b'.') {
|
|
||||||
end += 1;
|
|
||||||
}
|
|
||||||
let val = s[..end].parse().map_err(|_| "Cannot parse number literal".to_owned())?;
|
|
||||||
let unit = s[end..].parse().map_err(|_| "Invalid unit".to_owned())?;
|
|
||||||
Ok(Expression::NumberLiteral(val, unit))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_number_literal() {
|
|
||||||
fn doit(s: &str) -> Result<(f64, Unit), String> {
|
|
||||||
parse_number_literal(s.into()).map(|e| match e {
|
|
||||||
Expression::NumberLiteral(a, b) => (a, b),
|
|
||||||
_ => panic!(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(doit("10"), Ok((10., Unit::None)));
|
|
||||||
assert_eq!(doit("10phx"), Ok((10., Unit::Phx)));
|
|
||||||
assert_eq!(doit("10.0phx"), Ok((10., Unit::Phx)));
|
|
||||||
assert_eq!(doit("10.0"), Ok((10., Unit::None)));
|
|
||||||
assert_eq!(doit("1.1phx"), Ok((1.1, Unit::Phx)));
|
|
||||||
assert_eq!(doit("10.10"), Ok((10.10, Unit::None)));
|
|
||||||
assert_eq!(doit("10000000"), Ok((10000000., Unit::None)));
|
|
||||||
assert_eq!(doit("10000001phx"), Ok((10000001., Unit::Phx)));
|
|
||||||
|
|
||||||
let wrong_unit = Err("Invalid unit".to_owned());
|
|
||||||
let cannot_parse = Err("Cannot parse number literal".to_owned());
|
|
||||||
assert_eq!(doit("10000001 phx"), wrong_unit);
|
|
||||||
assert_eq!(doit("12.10.12phx"), cannot_parse);
|
|
||||||
assert_eq!(doit("12.12oo"), wrong_unit);
|
|
||||||
assert_eq!(doit("12.12€"), wrong_unit);
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ LICENSE END */
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use super::DocumentCache;
|
use super::DocumentCache;
|
||||||
use lsp_types::{GotoDefinitionResponse, LocationLink, Position, Range, Url};
|
use lsp_types::{GotoDefinitionResponse, LocationLink, Range, Url};
|
||||||
use sixtyfps_compilerlib::diagnostics::Spanned;
|
use sixtyfps_compilerlib::diagnostics::Spanned;
|
||||||
use sixtyfps_compilerlib::expression_tree::Expression;
|
use sixtyfps_compilerlib::expression_tree::Expression;
|
||||||
use sixtyfps_compilerlib::langtype::Type;
|
use sixtyfps_compilerlib::langtype::Type;
|
||||||
|
@ -191,26 +191,8 @@ fn goto_node(
|
||||||
) -> Option<GotoDefinitionResponse> {
|
) -> Option<GotoDefinitionResponse> {
|
||||||
let path = node.source_file.path();
|
let path = node.source_file.path();
|
||||||
let target_uri = Url::from_file_path(path).ok()?;
|
let target_uri = Url::from_file_path(path).ok()?;
|
||||||
let newline_offsets = match document_cache.newline_offsets.entry(target_uri.clone()) {
|
|
||||||
std::collections::hash_map::Entry::Occupied(e) => e.into_mut(),
|
|
||||||
std::collections::hash_map::Entry::Vacant(e) => e.insert(
|
|
||||||
DocumentCache::newline_offsets_from_content(&std::fs::read_to_string(path).ok()?),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
let offset = node.span().offset as u32;
|
let offset = node.span().offset as u32;
|
||||||
let pos = newline_offsets.binary_search(&offset).map_or_else(
|
let pos = document_cache.byte_offset_to_position(offset, &target_uri)?;
|
||||||
|line| {
|
|
||||||
if line == 0 {
|
|
||||||
Position::new(0, offset)
|
|
||||||
} else {
|
|
||||||
Position::new(
|
|
||||||
line as u32 - 1,
|
|
||||||
newline_offsets.get(line - 1).map_or(0, |x| offset - *x),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|line| Position::new(line as u32, 0),
|
|
||||||
);
|
|
||||||
let range = Range::new(pos, pos);
|
let range = Range::new(pos, pos);
|
||||||
Some(GotoDefinitionResponse::Link(vec![LocationLink {
|
Some(GotoDefinitionResponse::Link(vec![LocationLink {
|
||||||
origin_selection_range: None,
|
origin_selection_range: None,
|
||||||
|
|
|
@ -18,13 +18,14 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use lsp_server::{Connection, Message, Request, RequestId, Response};
|
use lsp_server::{Connection, Message, Request, RequestId, Response};
|
||||||
use lsp_types::notification::{DidChangeTextDocument, DidOpenTextDocument, Notification};
|
use lsp_types::notification::{DidChangeTextDocument, DidOpenTextDocument, Notification};
|
||||||
use lsp_types::request::{CodeActionRequest, ExecuteCommand, GotoDefinition};
|
use lsp_types::request::{CodeActionRequest, DocumentColor, ExecuteCommand, GotoDefinition};
|
||||||
use lsp_types::request::{Completion, HoverRequest};
|
use lsp_types::request::{Completion, HoverRequest};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
CodeActionOrCommand, CodeActionProviderCapability, Command, CompletionOptions,
|
CodeActionOrCommand, CodeActionProviderCapability, Color, ColorInformation, Command,
|
||||||
DidChangeTextDocumentParams, DidOpenTextDocumentParams, ExecuteCommandOptions, Hover,
|
CompletionOptions, DidChangeTextDocumentParams, DidOpenTextDocumentParams,
|
||||||
HoverProviderCapability, InitializeParams, OneOf, Position, PublishDiagnosticsParams, Range,
|
ExecuteCommandOptions, Hover, HoverProviderCapability, InitializeParams, OneOf, Position,
|
||||||
ServerCapabilities, TextDocumentSyncCapability, Url, WorkDoneProgressOptions,
|
PublishDiagnosticsParams, Range, ServerCapabilities, TextDocumentSyncCapability, Url,
|
||||||
|
WorkDoneProgressOptions,
|
||||||
};
|
};
|
||||||
use sixtyfps_compilerlib::diagnostics::BuildDiagnostics;
|
use sixtyfps_compilerlib::diagnostics::BuildDiagnostics;
|
||||||
use sixtyfps_compilerlib::langtype::Type;
|
use sixtyfps_compilerlib::langtype::Type;
|
||||||
|
@ -60,6 +61,35 @@ impl<'a> DocumentCache<'a> {
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn byte_offset_to_position(
|
||||||
|
&mut self,
|
||||||
|
offset: u32,
|
||||||
|
target_uri: &lsp_types::Url,
|
||||||
|
) -> Option<lsp_types::Position> {
|
||||||
|
let newline_offsets = match self.newline_offsets.entry(target_uri.clone()) {
|
||||||
|
std::collections::hash_map::Entry::Occupied(e) => e.into_mut(),
|
||||||
|
std::collections::hash_map::Entry::Vacant(e) => {
|
||||||
|
e.insert(Self::newline_offsets_from_content(
|
||||||
|
&std::fs::read_to_string(target_uri.to_file_path().ok()?).ok()?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let pos = newline_offsets.binary_search(&offset).map_or_else(
|
||||||
|
|line| {
|
||||||
|
if line == 0 {
|
||||||
|
Position::new(0, offset)
|
||||||
|
} else {
|
||||||
|
Position::new(
|
||||||
|
line as u32 - 1,
|
||||||
|
newline_offsets.get(line - 1).map_or(0, |x| offset - *x),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|line| Position::new(line as u32, 0),
|
||||||
|
);
|
||||||
|
Some(pos)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -97,6 +127,7 @@ fn run_lsp_server() -> Result<(), Error> {
|
||||||
commands: vec![SHOW_PREVIEW_COMMAND.into()],
|
commands: vec![SHOW_PREVIEW_COMMAND.into()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
|
color_provider: Some(true.into()),
|
||||||
..ServerCapabilities::default()
|
..ServerCapabilities::default()
|
||||||
};
|
};
|
||||||
let server_capabilities = serde_json::to_value(&capabilities).unwrap();
|
let server_capabilities = serde_json::to_value(&capabilities).unwrap();
|
||||||
|
@ -189,6 +220,9 @@ fn handle_request(
|
||||||
connection
|
connection
|
||||||
.sender
|
.sender
|
||||||
.send(Message::Response(Response::new_ok(id, None::<serde_json::Value>)))?;
|
.send(Message::Response(Response::new_ok(id, None::<serde_json::Value>)))?;
|
||||||
|
} else if let Some((id, params)) = cast::<DocumentColor>(&mut req) {
|
||||||
|
let result = get_document_color(document_cache, ¶ms.text_document).unwrap_or_default();
|
||||||
|
connection.sender.send(Message::Response(Response::new_ok(id, result)))?;
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -403,3 +437,40 @@ fn get_code_actions(
|
||||||
Some(vec![component.source_file.path().to_string_lossy().into(), component_name.into()]),
|
Some(vec![component.source_file.path().to_string_lossy().into(), component_name.into()]),
|
||||||
))])
|
))])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_document_color(
|
||||||
|
document_cache: &mut DocumentCache,
|
||||||
|
text_document: &lsp_types::TextDocumentIdentifier,
|
||||||
|
) -> Option<Vec<ColorInformation>> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let uri = &text_document.uri;
|
||||||
|
let doc = document_cache.documents.get_document(&uri.to_file_path().ok()?)?;
|
||||||
|
let root_node = &doc.node.as_ref()?.node;
|
||||||
|
let mut token = root_node.first_token()?;
|
||||||
|
loop {
|
||||||
|
if token.kind() == SyntaxKind::ColorLiteral {
|
||||||
|
(|| -> Option<()> {
|
||||||
|
let range = token.text_range();
|
||||||
|
let col = sixtyfps_compilerlib::literals::parse_color_literal(token.text())?;
|
||||||
|
let shift = |s: u32| -> f32 { ((col >> s) & 0xff) as f32 / 255. };
|
||||||
|
result.push(ColorInformation {
|
||||||
|
range: Range::new(
|
||||||
|
document_cache.byte_offset_to_position(range.start().into(), &uri)?,
|
||||||
|
document_cache.byte_offset_to_position(range.end().into(), &uri)?,
|
||||||
|
),
|
||||||
|
color: Color {
|
||||||
|
alpha: shift(24),
|
||||||
|
red: shift(16),
|
||||||
|
green: shift(8),
|
||||||
|
blue: shift(0),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Some(())
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
token = match token.next_token() {
|
||||||
|
Some(token) => token,
|
||||||
|
None => break Some(result),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue