vscode-css-languageservice 6.3.5 → 6.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -151,7 +151,7 @@ export const colorFunctions = [
151
151
  desc: l10n.t('css.builtin.lab', 'Creates a Color from Lightness, Chroma, Hue and alpha values of another Color.')
152
152
  }
153
153
  ];
154
- const colorFunctionNameRegExp = /^(rgb|rgba|hsl|hsla|hwb|lab|lch)$/i;
154
+ const colorFunctionNameRegExp = /^(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch)$/iu;
155
155
  export const colors = {
156
156
  aliceblue: '#f0f8ff',
157
157
  antiquewhite: '#faebd7',
@@ -322,26 +322,40 @@ function getNumericValue(node, factor, lowerLimit = 0, upperLimit = 1) {
322
322
  }
323
323
  throw new Error();
324
324
  }
325
+ const DEGREES_PER_CIRCLE = 360; // Number of degrees in a full circle
326
+ const GRAD_TO_DEGREE_FACTOR = 0.9; // Conversion factor: grads to degrees
327
+ const RADIANS_TO_DEGREES_FACTOR = DEGREES_PER_CIRCLE / 2 / Math.PI; // Conversion factor: radians to degrees
325
328
  function getAngle(node) {
326
- const val = node.getText();
327
- const m = val.match(/^([-+]?[0-9]*\.?[0-9]+)(deg|rad|grad|turn)?$/);
328
- if (m) {
329
- switch (m[2]) {
330
- case 'deg':
331
- return parseFloat(val) % 360;
332
- case 'rad':
333
- return (parseFloat(val) * 180 / Math.PI) % 360;
334
- case 'grad':
335
- return (parseFloat(val) * 0.9) % 360;
336
- case 'turn':
337
- return (parseFloat(val) * 360) % 360;
338
- default:
339
- if ('undefined' === typeof m[2]) {
340
- return parseFloat(val) % 360;
329
+ const textValue = node.getText();
330
+ // Hue angle keyword `none` is the equivilient of `0deg`
331
+ if (textValue === 'none') {
332
+ return 0;
333
+ }
334
+ const m = /^(?<numberString>[-+]?[0-9]*\.?[0-9]+)(?<unit>deg|rad|grad|turn)?$/iu.exec(textValue);
335
+ if (m?.groups?.['numberString']) {
336
+ const value = Number.parseFloat(m.groups['numberString']);
337
+ if (!Number.isNaN(value)) {
338
+ switch (m.groups['unit']) {
339
+ case 'deg': {
340
+ return value % DEGREES_PER_CIRCLE;
341
+ }
342
+ case 'grad': {
343
+ return (value * GRAD_TO_DEGREE_FACTOR) % DEGREES_PER_CIRCLE;
344
+ }
345
+ case 'rad': {
346
+ return (value * RADIANS_TO_DEGREES_FACTOR) % DEGREES_PER_CIRCLE;
347
+ }
348
+ case 'turn': {
349
+ return (value * DEGREES_PER_CIRCLE) % DEGREES_PER_CIRCLE;
341
350
  }
351
+ default: {
352
+ // Unitless angles are treated as degrees
353
+ return value % DEGREES_PER_CIRCLE;
354
+ }
355
+ }
342
356
  }
343
357
  }
344
- throw new Error();
358
+ throw new Error(`Failed to parse '${textValue}' as angle`);
345
359
  }
346
360
  export function isColorConstructor(node) {
347
361
  const name = node.getName();
@@ -560,6 +574,29 @@ export function xyzFromLAB(lab) {
560
574
  xyz.z = xyz.z * 108.883;
561
575
  return xyz;
562
576
  }
577
+ export function xyzFromOKLAB(lab) {
578
+ // Convert from OKLab to XYZ
579
+ // References: https://bottosson.github.io/posts/oklab/
580
+ // lab.l is in 0-1 range
581
+ // lab.a and lab.b are in -0.4 to 0.4 range
582
+ const l = lab.l + 0.3963377774 * lab.a + 0.2158037573 * lab.b;
583
+ const m = lab.l - 0.1055613458 * lab.a - 0.0638541728 * lab.b;
584
+ const s = lab.l - 0.0894841775 * lab.a - 1.291485548 * lab.b;
585
+ // Apply non-linearity using exponentiation
586
+ const l3 = l ** 3;
587
+ const m3 = m ** 3;
588
+ const s3 = s ** 3;
589
+ // Convert to XYZ
590
+ const x = 1.2270138511 * l3 - 0.5577999807 * m3 + 0.281256149 * s3;
591
+ const y = -0.0405801784 * l3 + 1.1122568696 * m3 - 0.0716766787 * s3;
592
+ const z = -0.0763812845 * l3 - 0.4214819784 * m3 + 1.5861632204 * s3;
593
+ return {
594
+ x: x * 100,
595
+ y: y * 100,
596
+ z: z * 100,
597
+ alpha: lab.alpha ?? 1,
598
+ };
599
+ }
563
600
  export function xyzToRGB(xyz) {
564
601
  const x = xyz.x / 100;
565
602
  const y = xyz.y / 100;
@@ -645,53 +682,124 @@ export function XYZtoLAB(xyz, round = true) {
645
682
  };
646
683
  }
647
684
  }
685
+ export function XYZtoOKLAB(xyz, round = true) {
686
+ // Convert XYZ to OKLab
687
+ // References: https://bottosson.github.io/posts/oklab/
688
+ // Normalize XYZ values
689
+ const x = xyz.x / 100;
690
+ const y = xyz.y / 100;
691
+ const z = xyz.z / 100;
692
+ // Convert to LMS
693
+ const l = 0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z;
694
+ const m = 0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z;
695
+ const s = 0.0482003018 * x + 0.2643662691 * y + 0.633851707 * z;
696
+ // Apply non-linearity
697
+ const l_ = Math.cbrt(l);
698
+ const m_ = Math.cbrt(m);
699
+ const s_ = Math.cbrt(s);
700
+ // Convert to OKLab
701
+ const L = 0.2104542553 * l_ + 0.793617785 * m_ - 0.0040720468 * s_;
702
+ const a = 1.9779984951 * l_ - 2.428592205 * m_ + 0.4505937099 * s_;
703
+ const b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.808675766 * s_;
704
+ return round
705
+ // 5 decimal places for precision
706
+ ? {
707
+ l: Number(L.toFixed(5)),
708
+ a: Number(a.toFixed(5)),
709
+ b: Number(b.toFixed(5)),
710
+ alpha: xyz.alpha,
711
+ }
712
+ : {
713
+ l: L,
714
+ a,
715
+ b,
716
+ alpha: xyz.alpha,
717
+ };
718
+ }
648
719
  export function labFromColor(rgba, round = true) {
649
720
  const xyz = RGBtoXYZ(rgba);
650
721
  const lab = XYZtoLAB(xyz, round);
651
722
  return lab;
652
723
  }
653
- export function lchFromColor(rgba) {
654
- const lab = labFromColor(rgba, false);
724
+ export function oklabFromColor(rgba, round = true) {
725
+ const xyz = RGBtoXYZ(rgba);
726
+ const lab = XYZtoOKLAB(xyz, round);
727
+ // Convert lightness to a percentage of oklab
728
+ return { ...lab, l: lab.l * 100 };
729
+ }
730
+ /**
731
+ * Calculate chroma and hue from Lab values
732
+ * Returns LCH values without formatting/rounding
733
+ */
734
+ function labToLCH(lab) {
655
735
  const c = Math.sqrt(Math.pow(lab.a, 2) + Math.pow(lab.b, 2));
656
- let h = Math.atan2(lab.b, lab.a) * (180 / Math.PI);
736
+ let h = Math.atan2(lab.b, lab.a) * RADIANS_TO_DEGREES_FACTOR;
657
737
  while (h < 0) {
658
738
  h = h + 360;
659
739
  }
660
740
  return {
661
- l: Math.round((lab.l + Number.EPSILON) * 100) / 100,
662
- c: Math.round((c + Number.EPSILON) * 100) / 100,
663
- h: Math.round((h + Number.EPSILON) * 100) / 100,
664
- alpha: lab.alpha
741
+ l: lab.l,
742
+ c: c,
743
+ h: h,
744
+ alpha: lab.alpha,
665
745
  };
666
746
  }
667
- export function colorFromLAB(l, a, b, alpha = 1.0) {
668
- const lab = {
669
- l,
670
- a,
671
- b,
672
- alpha
747
+ export function lchFromColor(rgba) {
748
+ const lab = labFromColor(rgba, false);
749
+ const lch = labToLCH(lab);
750
+ return {
751
+ l: Math.round((lch.l + Number.EPSILON) * 100) / 100,
752
+ c: Math.round((lch.c + Number.EPSILON) * 100) / 100,
753
+ h: Math.round((lch.h + Number.EPSILON) * 100) / 100,
754
+ alpha: lch.alpha,
755
+ };
756
+ }
757
+ export function oklchFromColor(rgba) {
758
+ const lab = oklabFromColor(rgba, false);
759
+ const lch = labToLCH(lab);
760
+ return {
761
+ l: Number((lch.l).toFixed(3)),
762
+ c: Number(lch.c.toFixed(5)),
763
+ h: Number(lch.h.toFixed(3)),
764
+ alpha: lch.alpha,
673
765
  };
674
- const xyz = xyzFromLAB(lab);
766
+ }
767
+ /**
768
+ * Generic function to convert LAB/OKLAB to Color
769
+ */
770
+ function labToColor(lab, xyzConverter) {
771
+ const xyz = xyzConverter(lab);
675
772
  const rgb = xyzToRGB(xyz);
676
773
  return {
677
- red: (rgb.red >= 0 ? (rgb.red <= 255 ? rgb.red : 255) : 0) / 255.0,
678
- green: (rgb.green >= 0 ? (rgb.green <= 255 ? rgb.green : 255) : 0) / 255.0,
679
- blue: (rgb.blue >= 0 ? (rgb.blue <= 255 ? rgb.blue : 255) : 0) / 255.0,
680
- alpha
774
+ red: (rgb.red >= 0 ? Math.min(rgb.red, 255) : 0) / 255,
775
+ green: (rgb.green >= 0 ? Math.min(rgb.green, 255) : 0) / 255,
776
+ blue: (rgb.blue >= 0 ? Math.min(rgb.blue, 255) : 0) / 255,
777
+ alpha: lab.alpha ?? 1,
681
778
  };
682
779
  }
683
- export function labFromLCH(l, c, h, alpha = 1.0) {
780
+ export function colorFromLAB(l, a, b, alpha = 1) {
781
+ return labToColor({ l, a, b, alpha }, xyzFromLAB);
782
+ }
783
+ export function colorFromOKLAB(l, a, b, alpha = 1) {
784
+ return labToColor({ l, a, b, alpha }, xyzFromOKLAB);
785
+ }
786
+ const DEGREES_TO_RADIANS_FACTOR = Math.PI / 180;
787
+ export function labFromLCH(l, c, h, alpha = 1) {
684
788
  return {
685
789
  l: l,
686
- a: c * Math.cos(h * (Math.PI / 180)),
687
- b: c * Math.sin(h * (Math.PI / 180)),
688
- alpha: alpha
790
+ a: c * Math.cos(h * DEGREES_TO_RADIANS_FACTOR),
791
+ b: c * Math.sin(h * DEGREES_TO_RADIANS_FACTOR),
792
+ alpha: alpha,
689
793
  };
690
794
  }
691
- export function colorFromLCH(l, c, h, alpha = 1.0) {
795
+ export function colorFromLCH(l, c, h, alpha = 1) {
692
796
  const lab = labFromLCH(l, c, h, alpha);
693
797
  return colorFromLAB(lab.l, lab.a, lab.b, alpha);
694
798
  }
799
+ export function colorFromOKLCH(l, c, h, alpha = 1) {
800
+ const lab = labFromLCH(l, c, h, alpha); // Conversion is the same as LCH->LAB for OKLCH-OKLAB
801
+ return colorFromOKLAB(lab.l, lab.a, lab.b, alpha);
802
+ }
695
803
  export function getColorValue(node) {
696
804
  if (node.type === nodes.NodeType.HexColorValue) {
697
805
  const text = node.getText();
@@ -721,42 +829,59 @@ export function getColorValue(node) {
721
829
  }
722
830
  try {
723
831
  const alpha = colorValues.length === 4 ? getNumericValue(colorValues[3], 1) : 1;
724
- if (name === 'rgb' || name === 'rgba') {
725
- return {
726
- red: getNumericValue(colorValues[0], 255.0),
727
- green: getNumericValue(colorValues[1], 255.0),
728
- blue: getNumericValue(colorValues[2], 255.0),
729
- alpha
730
- };
731
- }
732
- else if (name === 'hsl' || name === 'hsla') {
733
- const h = getAngle(colorValues[0]);
734
- const s = getNumericValue(colorValues[1], 100.0);
735
- const l = getNumericValue(colorValues[2], 100.0);
736
- return colorFromHSL(h, s, l, alpha);
737
- }
738
- else if (name === 'hwb') {
739
- const h = getAngle(colorValues[0]);
740
- const w = getNumericValue(colorValues[1], 100.0);
741
- const b = getNumericValue(colorValues[2], 100.0);
742
- return colorFromHWB(h, w, b, alpha);
743
- }
744
- else if (name === 'lab') {
745
- // Reference: https://mina86.com/2021/srgb-lab-lchab-conversions/
746
- const l = getNumericValue(colorValues[0], 100.0);
747
- // Since these two values can be negative, a lower limit of -1 has been added
748
- const a = getNumericValue(colorValues[1], 125.0, -1);
749
- const b = getNumericValue(colorValues[2], 125.0, -1);
750
- return colorFromLAB(l * 100, a * 125, b * 125, alpha);
751
- }
752
- else if (name === 'lch') {
753
- const l = getNumericValue(colorValues[0], 100.0);
754
- const c = getNumericValue(colorValues[1], 230.0);
755
- const h = getAngle(colorValues[2]);
756
- return colorFromLCH(l * 100, c * 230, h, alpha);
832
+ switch (name) {
833
+ case 'rgb':
834
+ case 'rgba': {
835
+ return {
836
+ red: getNumericValue(colorValues[0], 255),
837
+ green: getNumericValue(colorValues[1], 255),
838
+ blue: getNumericValue(colorValues[2], 255),
839
+ alpha,
840
+ };
841
+ }
842
+ case 'hsl':
843
+ case 'hsla': {
844
+ const h = getAngle(colorValues[0]);
845
+ const s = getNumericValue(colorValues[1], 100);
846
+ const l = getNumericValue(colorValues[2], 100);
847
+ return colorFromHSL(h, s, l, alpha);
848
+ }
849
+ case 'hwb': {
850
+ const h = getAngle(colorValues[0]);
851
+ const w = getNumericValue(colorValues[1], 100);
852
+ const b = getNumericValue(colorValues[2], 100);
853
+ return colorFromHWB(h, w, b, alpha);
854
+ }
855
+ case 'lab': {
856
+ // Reference: https://mina86.com/2021/srgb-lab-lchab-conversions/
857
+ const l = getNumericValue(colorValues[0], 100);
858
+ // Since these two values can be negative, a lower limit of -1 has been added
859
+ const a = getNumericValue(colorValues[1], 125, -1);
860
+ const b = getNumericValue(colorValues[2], 125, -1);
861
+ return colorFromLAB(l * 100, a * 125, b * 125, alpha);
862
+ }
863
+ case 'lch': {
864
+ const l = getNumericValue(colorValues[0], 100);
865
+ const c = getNumericValue(colorValues[1], 230);
866
+ const h = getAngle(colorValues[2]);
867
+ return colorFromLCH(l * 100, c * 230, h, alpha);
868
+ }
869
+ case 'oklab': {
870
+ const l = getNumericValue(colorValues[0], 1);
871
+ // Since these two values can be negative, a lower limit of -1 has been added
872
+ const a = getNumericValue(colorValues[1], 0.4, -1);
873
+ const b = getNumericValue(colorValues[2], 0.4, -1);
874
+ return colorFromOKLAB(l, a * 0.4, b * 0.4, alpha);
875
+ }
876
+ case 'oklch': {
877
+ const l = getNumericValue(colorValues[0], 1);
878
+ const c = getNumericValue(colorValues[1], 0.4);
879
+ const h = getAngle(colorValues[2]);
880
+ return colorFromOKLCH(l, c * 0.4, h, alpha);
881
+ }
757
882
  }
758
883
  }
759
- catch (e) {
884
+ catch {
760
885
  // parse error on numeric value
761
886
  return null;
762
887
  }
@@ -139,6 +139,9 @@ export class CSSCompletion {
139
139
  else if (node instanceof nodes.SupportsCondition) {
140
140
  this.getCompletionsForSupportsCondition(node, result);
141
141
  }
142
+ else if (node instanceof nodes.MediaCondition) {
143
+ this.getCompletionsForMediaCondition(node, result);
144
+ }
142
145
  else if (node instanceof nodes.ExtendsReference) {
143
146
  this.getCompletionsForExtendsReference(node, null, result);
144
147
  }
@@ -907,6 +910,46 @@ export class CSSCompletion {
907
910
  }
908
911
  return this.getCompletionForTopLevel(result);
909
912
  }
913
+ getCompletionsForMediaCondition(mediaCondition, result) {
914
+ const child = mediaCondition.findFirstChildBeforeOffset(this.offset);
915
+ if (child) {
916
+ if (child instanceof nodes.MediaFeature) {
917
+ const featureName = child.getChild(0);
918
+ if (featureName && this.offset > featureName.end) {
919
+ const name = featureName.getText();
920
+ const media = this.cssDataManager.getAtDirective('@media')?.descriptors?.find((descriptor) => descriptor.name === name);
921
+ if (media) {
922
+ return this.getValueEnumProposals(media, null, result);
923
+ }
924
+ }
925
+ }
926
+ else if (child instanceof nodes.MediaCondition) {
927
+ return this.getCompletionsForMediaCondition(child, result);
928
+ }
929
+ }
930
+ // Return all media descriptors
931
+ const media = this.cssDataManager.getAtDirective('@media');
932
+ for (const descriptor of media?.descriptors || []) {
933
+ let command = undefined;
934
+ let text = descriptor.name;
935
+ if (descriptor.type === 'discrete') {
936
+ // Change this check when we support auto completes for other types
937
+ if (descriptor.values) {
938
+ command = retriggerCommand;
939
+ }
940
+ text = `${descriptor.name}: `;
941
+ }
942
+ result.items.push({
943
+ label: descriptor.name,
944
+ textEdit: TextEdit.replace(this.getCompletionRange(null), text),
945
+ documentation: languageFacts.getEntryDescription(descriptor, this.doesSupportMarkdown()),
946
+ tags: isDeprecated(descriptor) ? [CompletionItemTag.Deprecated] : [],
947
+ kind: CompletionItemKind.Keyword,
948
+ command,
949
+ });
950
+ }
951
+ return result;
952
+ }
910
953
  getCompletionsForExtendsReference(extendsRef, existingNode, result) {
911
954
  return result;
912
955
  }
@@ -7,7 +7,7 @@ import { DocumentHighlightKind, Location, Range, SymbolKind, TextEdit, FileType
7
7
  import * as l10n from '@vscode/l10n';
8
8
  import * as nodes from '../parser/cssNodes';
9
9
  import { Symbols } from '../parser/cssSymbolScope';
10
- import { getColorValue, hslFromColor, hwbFromColor, labFromColor, lchFromColor } from '../languageFacts/facts';
10
+ import { getColorValue, hslFromColor, hwbFromColor, labFromColor, lchFromColor, oklabFromColor, oklchFromColor, } from '../languageFacts/facts';
11
11
  import { startsWith } from '../utils/strings';
12
12
  import { dirname, joinPath } from '../utils/resources';
13
13
  const startsWithSchemeRegex = /^\w+:\/\//;
@@ -321,13 +321,19 @@ export class CSSNavigation {
321
321
  }
322
322
  result.push({ label: label, textEdit: TextEdit.replace(range, label) });
323
323
  const lch = lchFromColor(color);
324
- if (lab.alpha === 1) {
324
+ if (lch.alpha === 1) {
325
325
  label = `lch(${lch.l}% ${lch.c} ${lch.h})`;
326
326
  }
327
327
  else {
328
328
  label = `lch(${lch.l}% ${lch.c} ${lch.h} / ${lch.alpha})`;
329
329
  }
330
330
  result.push({ label: label, textEdit: TextEdit.replace(range, label) });
331
+ const oklab = oklabFromColor(color);
332
+ label = (oklab.alpha === 1) ? `oklab(${oklab.l}% ${oklab.a} ${oklab.b})` : `oklab(${oklab.l}% ${oklab.a} ${oklab.b} / ${oklab.alpha})`;
333
+ result.push({ label: label, textEdit: TextEdit.replace(range, label) });
334
+ const oklch = oklchFromColor(color);
335
+ label = (oklch.alpha === 1) ? `oklch(${oklch.l}% ${oklch.c} ${oklch.h})` : `oklch(${oklch.l}% ${oklch.c} ${oklch.h} / ${oklch.alpha})`;
336
+ result.push({ label: label, textEdit: TextEdit.replace(range, label) });
331
337
  return result;
332
338
  }
333
339
  prepareRename(document, position, stylesheet) {
@@ -134,6 +134,17 @@ export interface IPropertyData {
134
134
  relevance?: number;
135
135
  atRule?: string;
136
136
  }
137
+ export interface IDescriptorData {
138
+ name: string;
139
+ description?: string;
140
+ references?: IReference[];
141
+ syntax?: string;
142
+ type?: string;
143
+ values?: IValueData[];
144
+ browsers?: string[];
145
+ baseline?: BaselineStatus;
146
+ status?: EntryStatus;
147
+ }
137
148
  export interface IAtDirectiveData {
138
149
  name: string;
139
150
  description?: string | MarkupContent;
@@ -141,6 +152,7 @@ export interface IAtDirectiveData {
141
152
  baseline?: BaselineStatus;
142
153
  status?: EntryStatus;
143
154
  references?: IReference[];
155
+ descriptors?: IDescriptorData[];
144
156
  }
145
157
  export interface IPseudoClassData {
146
158
  name: string;