@hyperdrive.bot/paseo-highlight 0.2.5

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.
@@ -0,0 +1,4 @@
1
+ import type { HighlightStyle } from "./types.js";
2
+ export declare const darkHighlightColors: Record<HighlightStyle, string>;
3
+ export declare const lightHighlightColors: Record<HighlightStyle, string>;
4
+ //# sourceMappingURL=colors.d.ts.map
package/dist/colors.js ADDED
@@ -0,0 +1,45 @@
1
+ export const darkHighlightColors = {
2
+ keyword: "#ff7b72",
3
+ comment: "#8b949e",
4
+ string: "#a5d6ff",
5
+ number: "#79c0ff",
6
+ literal: "#79c0ff",
7
+ function: "#d2a8ff",
8
+ definition: "#d2a8ff",
9
+ class: "#ffa657",
10
+ type: "#ff7b72",
11
+ tag: "#7ee787",
12
+ attribute: "#79c0ff",
13
+ property: "#79c0ff",
14
+ variable: "#c9d1d9",
15
+ operator: "#79c0ff",
16
+ punctuation: "#c9d1d9",
17
+ regexp: "#a5d6ff",
18
+ escape: "#79c0ff",
19
+ meta: "#8b949e",
20
+ heading: "#79c0ff",
21
+ link: "#a5d6ff",
22
+ };
23
+ export const lightHighlightColors = {
24
+ keyword: "#cf222e",
25
+ comment: "#6e7781",
26
+ string: "#0a3069",
27
+ number: "#0550ae",
28
+ literal: "#0550ae",
29
+ function: "#8250df",
30
+ definition: "#8250df",
31
+ class: "#953800",
32
+ type: "#cf222e",
33
+ tag: "#116329",
34
+ attribute: "#0550ae",
35
+ property: "#0550ae",
36
+ variable: "#24292f",
37
+ operator: "#0550ae",
38
+ punctuation: "#24292f",
39
+ regexp: "#0a3069",
40
+ escape: "#0550ae",
41
+ meta: "#6e7781",
42
+ heading: "#0550ae",
43
+ link: "#0a3069",
44
+ };
45
+ //# sourceMappingURL=colors.js.map
@@ -0,0 +1,4 @@
1
+ import type { HighlightToken } from "./types.js";
2
+ export declare function highlightCode(code: string, filename: string): HighlightToken[][];
3
+ export declare function highlightLine(line: string, filename: string): HighlightToken[];
4
+ //# sourceMappingURL=highlighter.d.ts.map
@@ -0,0 +1,97 @@
1
+ import { highlightTree, tagHighlighter, tags } from "@lezer/highlight";
2
+ import { getParserForFile } from "./parsers.js";
3
+ const highlighter = tagHighlighter([
4
+ { tag: tags.keyword, class: "keyword" },
5
+ { tag: tags.controlKeyword, class: "keyword" },
6
+ { tag: tags.operatorKeyword, class: "keyword" },
7
+ { tag: tags.definitionKeyword, class: "keyword" },
8
+ { tag: tags.moduleKeyword, class: "keyword" },
9
+ { tag: tags.comment, class: "comment" },
10
+ { tag: tags.lineComment, class: "comment" },
11
+ { tag: tags.blockComment, class: "comment" },
12
+ { tag: tags.docComment, class: "comment" },
13
+ { tag: tags.string, class: "string" },
14
+ { tag: tags.special(tags.string), class: "string" },
15
+ { tag: tags.number, class: "number" },
16
+ { tag: tags.integer, class: "number" },
17
+ { tag: tags.float, class: "number" },
18
+ { tag: tags.bool, class: "literal" },
19
+ { tag: tags.null, class: "literal" },
20
+ { tag: tags.function(tags.variableName), class: "function" },
21
+ { tag: tags.function(tags.propertyName), class: "function" },
22
+ { tag: tags.definition(tags.variableName), class: "definition" },
23
+ { tag: tags.definition(tags.propertyName), class: "definition" },
24
+ { tag: tags.definition(tags.function(tags.variableName)), class: "definition" },
25
+ { tag: tags.className, class: "class" },
26
+ { tag: tags.definition(tags.className), class: "class" },
27
+ { tag: tags.typeName, class: "type" },
28
+ { tag: tags.tagName, class: "tag" },
29
+ { tag: tags.attributeName, class: "attribute" },
30
+ { tag: tags.attributeValue, class: "string" },
31
+ { tag: tags.propertyName, class: "property" },
32
+ { tag: tags.variableName, class: "variable" },
33
+ { tag: tags.local(tags.variableName), class: "variable" },
34
+ { tag: tags.special(tags.variableName), class: "variable" },
35
+ { tag: tags.operator, class: "operator" },
36
+ { tag: tags.punctuation, class: "punctuation" },
37
+ { tag: tags.bracket, class: "punctuation" },
38
+ { tag: tags.separator, class: "punctuation" },
39
+ { tag: tags.regexp, class: "regexp" },
40
+ { tag: tags.escape, class: "escape" },
41
+ { tag: tags.meta, class: "meta" },
42
+ { tag: tags.heading, class: "heading" },
43
+ { tag: tags.link, class: "link" },
44
+ { tag: tags.url, class: "link" },
45
+ ]);
46
+ export function highlightCode(code, filename) {
47
+ const parser = getParserForFile(filename);
48
+ if (!parser) {
49
+ return code.split("\n").map((line) => [{ text: line, style: null }]);
50
+ }
51
+ const tree = parser.parse(code);
52
+ const lines = code.split("\n");
53
+ const result = [];
54
+ for (let i = 0; i < lines.length; i++) {
55
+ result.push([]);
56
+ }
57
+ // Build a map of character positions to styles
58
+ const styleMap = Array.from({ length: code.length }, () => null);
59
+ highlightTree(tree, highlighter, (from, to, classes) => {
60
+ for (let i = from; i < to && i < styleMap.length; i++) {
61
+ styleMap[i] = classes;
62
+ }
63
+ });
64
+ // Convert style map to tokens per line
65
+ let pos = 0;
66
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
67
+ const line = lines[lineIndex];
68
+ if (line.length === 0) {
69
+ result[lineIndex].push({ text: "", style: null });
70
+ pos++; // skip newline
71
+ continue;
72
+ }
73
+ let currentToken = { text: "", style: styleMap[pos] };
74
+ for (let i = 0; i < line.length; i++) {
75
+ const charStyle = styleMap[pos + i];
76
+ if (charStyle === currentToken.style) {
77
+ currentToken.text += line[i];
78
+ }
79
+ else {
80
+ if (currentToken.text) {
81
+ result[lineIndex].push(currentToken);
82
+ }
83
+ currentToken = { text: line[i], style: charStyle };
84
+ }
85
+ }
86
+ if (currentToken.text) {
87
+ result[lineIndex].push(currentToken);
88
+ }
89
+ pos += line.length + 1; // +1 for newline
90
+ }
91
+ return result;
92
+ }
93
+ export function highlightLine(line, filename) {
94
+ const result = highlightCode(line, filename);
95
+ return result[0] ?? [{ text: line, style: null }];
96
+ }
97
+ //# sourceMappingURL=highlighter.js.map
@@ -0,0 +1,7 @@
1
+ export type { HighlightStyle, HighlightToken } from "./types.js";
2
+ export { getParserForFile, isLanguageSupported, getSupportedExtensions } from "./parsers.js";
3
+ export { highlightCode, highlightLine } from "./highlighter.js";
4
+ export { darkHighlightColors, lightHighlightColors } from "./colors.js";
5
+ export type { SyntaxThemeId, SyntaxThemeOption, SyntaxColors } from "./themes.js";
6
+ export { SYNTAX_THEME_IDS, SYNTAX_THEME_OPTIONS, isSyntaxThemeId, resolveSyntaxColors, } from "./themes.js";
7
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { getParserForFile, isLanguageSupported, getSupportedExtensions } from "./parsers.js";
2
+ export { highlightCode, highlightLine } from "./highlighter.js";
3
+ export { darkHighlightColors, lightHighlightColors } from "./colors.js";
4
+ export { SYNTAX_THEME_IDS, SYNTAX_THEME_OPTIONS, isSyntaxThemeId, resolveSyntaxColors, } from "./themes.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,5 @@
1
+ import type { Parser } from "@lezer/common";
2
+ export declare function getParserForFile(filename: string): Parser | null;
3
+ export declare function isLanguageSupported(filename: string): boolean;
4
+ export declare function getSupportedExtensions(): string[];
5
+ //# sourceMappingURL=parsers.d.ts.map
@@ -0,0 +1,85 @@
1
+ import { StreamLanguage } from "@codemirror/language";
2
+ import { dart } from "@codemirror/legacy-modes/mode/clike";
3
+ import { swift } from "@codemirror/legacy-modes/mode/swift";
4
+ import { parser as jsParser } from "@lezer/javascript";
5
+ import { parser as jsonParser } from "@lezer/json";
6
+ import { parser as cssParser } from "@lezer/css";
7
+ import { parser as cppParser } from "@lezer/cpp";
8
+ import { parser as goParser } from "@lezer/go";
9
+ import { parser as htmlParser } from "@lezer/html";
10
+ import { parser as javaParser } from "@lezer/java";
11
+ import { parser as pythonParser } from "@lezer/python";
12
+ import { parser as markdownParser } from "@lezer/markdown";
13
+ import { parser as phpParser } from "@lezer/php";
14
+ import { parser as rustParser } from "@lezer/rust";
15
+ import { parser as xmlParser } from "@lezer/xml";
16
+ import { parser as yamlParser } from "@lezer/yaml";
17
+ import { csharpLanguage } from "@replit/codemirror-lang-csharp";
18
+ import { parser as elixirParser } from "lezer-elixir";
19
+ const parsersByExtension = {
20
+ // JavaScript/TypeScript
21
+ js: jsParser,
22
+ jsx: jsParser.configure({ dialect: "jsx" }),
23
+ ts: jsParser.configure({ dialect: "ts" }),
24
+ tsx: jsParser.configure({ dialect: "ts jsx" }),
25
+ mjs: jsParser,
26
+ cjs: jsParser,
27
+ // C / C++ / Objective-C
28
+ c: cppParser,
29
+ h: cppParser,
30
+ cc: cppParser,
31
+ cpp: cppParser,
32
+ cxx: cppParser,
33
+ hpp: cppParser,
34
+ hxx: cppParser,
35
+ m: cppParser,
36
+ mm: cppParser,
37
+ // JSON
38
+ json: jsonParser,
39
+ // CSS
40
+ css: cssParser,
41
+ scss: cssParser,
42
+ // HTML
43
+ html: htmlParser,
44
+ htm: htmlParser,
45
+ // XML
46
+ xml: xmlParser,
47
+ // Java
48
+ java: javaParser,
49
+ // Python
50
+ py: pythonParser,
51
+ // Go
52
+ go: goParser,
53
+ // PHP
54
+ php: phpParser,
55
+ // YAML
56
+ yaml: yamlParser,
57
+ yml: yamlParser,
58
+ // Rust
59
+ rs: rustParser,
60
+ // Swift
61
+ swift: StreamLanguage.define(swift).parser,
62
+ // Dart
63
+ dart: StreamLanguage.define(dart).parser,
64
+ // C#
65
+ cs: csharpLanguage.parser,
66
+ // Elixir
67
+ ex: elixirParser,
68
+ exs: elixirParser,
69
+ // Markdown
70
+ md: markdownParser,
71
+ mdx: markdownParser,
72
+ };
73
+ export function getParserForFile(filename) {
74
+ const ext = filename.split(".").pop()?.toLowerCase();
75
+ if (!ext)
76
+ return null;
77
+ return parsersByExtension[ext] ?? null;
78
+ }
79
+ export function isLanguageSupported(filename) {
80
+ return getParserForFile(filename) !== null;
81
+ }
82
+ export function getSupportedExtensions() {
83
+ return Object.keys(parsersByExtension);
84
+ }
85
+ //# sourceMappingURL=parsers.js.map
@@ -0,0 +1,12 @@
1
+ import type { HighlightStyle } from "./types.js";
2
+ export type SyntaxThemeId = "github" | "catppuccin" | "dracula" | "tokyo-night" | "one" | "nord" | "gruvbox" | "solarized";
3
+ export declare const SYNTAX_THEME_IDS: readonly SyntaxThemeId[];
4
+ export interface SyntaxThemeOption {
5
+ id: SyntaxThemeId;
6
+ label: string;
7
+ }
8
+ export declare const SYNTAX_THEME_OPTIONS: readonly SyntaxThemeOption[];
9
+ export type SyntaxColors = Record<HighlightStyle, string>;
10
+ export declare function isSyntaxThemeId(value: string): value is SyntaxThemeId;
11
+ export declare function resolveSyntaxColors(id: SyntaxThemeId, colorScheme: "light" | "dark"): SyntaxColors;
12
+ //# sourceMappingURL=themes.d.ts.map
package/dist/themes.js ADDED
@@ -0,0 +1,235 @@
1
+ import { darkHighlightColors, lightHighlightColors } from "./colors.js";
2
+ export const SYNTAX_THEME_IDS = [
3
+ "github",
4
+ "catppuccin",
5
+ "dracula",
6
+ "tokyo-night",
7
+ "one",
8
+ "nord",
9
+ "gruvbox",
10
+ "solarized",
11
+ ];
12
+ export const SYNTAX_THEME_OPTIONS = [
13
+ { id: "github", label: "GitHub" },
14
+ { id: "catppuccin", label: "Catppuccin" },
15
+ { id: "dracula", label: "Dracula" },
16
+ { id: "tokyo-night", label: "Tokyo Night" },
17
+ { id: "one", label: "One" },
18
+ { id: "nord", label: "Nord" },
19
+ { id: "gruvbox", label: "Gruvbox" },
20
+ { id: "solarized", label: "Solarized" },
21
+ ];
22
+ function expandRolePalette(r) {
23
+ return {
24
+ keyword: r.keyword,
25
+ comment: r.comment,
26
+ string: r.string,
27
+ number: r.number,
28
+ literal: r.number,
29
+ function: r.function,
30
+ definition: r.function,
31
+ class: r.type,
32
+ type: r.type,
33
+ tag: r.tag,
34
+ attribute: r.attribute,
35
+ property: r.attribute,
36
+ variable: r.base,
37
+ operator: r.operator,
38
+ punctuation: r.base,
39
+ regexp: r.string,
40
+ escape: r.number,
41
+ meta: r.comment,
42
+ heading: r.function,
43
+ link: r.string,
44
+ };
45
+ }
46
+ // --- Catppuccin (Latte light / Mocha dark) -------------------------------
47
+ const catppuccinLatte = {
48
+ base: "#4c4f69",
49
+ keyword: "#8839ef",
50
+ comment: "#8c8fa1",
51
+ string: "#40a02b",
52
+ number: "#fe640b",
53
+ function: "#1e66f5",
54
+ type: "#df8e1d",
55
+ tag: "#8839ef",
56
+ attribute: "#df8e1d",
57
+ operator: "#04a5e5",
58
+ };
59
+ const catppuccinMocha = {
60
+ base: "#cdd6f4",
61
+ keyword: "#cba6f7",
62
+ comment: "#9399b2",
63
+ string: "#a6e3a1",
64
+ number: "#fab387",
65
+ function: "#89b4fa",
66
+ type: "#f9e2af",
67
+ tag: "#cba6f7",
68
+ attribute: "#f9e2af",
69
+ operator: "#89dceb",
70
+ };
71
+ // --- Dracula (dark only) -------------------------------------------------
72
+ const dracula = {
73
+ base: "#f8f8f2",
74
+ keyword: "#ff79c6",
75
+ comment: "#6272a4",
76
+ string: "#f1fa8c",
77
+ number: "#bd93f9",
78
+ function: "#50fa7b",
79
+ type: "#8be9fd",
80
+ tag: "#ff79c6",
81
+ attribute: "#50fa7b",
82
+ operator: "#ff79c6",
83
+ };
84
+ // --- Tokyo Night (Day light / Night dark) --------------------------------
85
+ const tokyoDay = {
86
+ base: "#3760bf",
87
+ keyword: "#9854f1",
88
+ comment: "#848cb5",
89
+ string: "#587539",
90
+ number: "#b15c00",
91
+ function: "#2e7de9",
92
+ type: "#007197",
93
+ tag: "#f52a65",
94
+ attribute: "#8c6c3e",
95
+ operator: "#006a83",
96
+ };
97
+ const tokyoNight = {
98
+ base: "#c0caf5",
99
+ keyword: "#bb9af7",
100
+ comment: "#565f89",
101
+ string: "#9ece6a",
102
+ number: "#ff9e64",
103
+ function: "#7aa2f7",
104
+ type: "#2ac3de",
105
+ tag: "#f7768e",
106
+ attribute: "#e0af68",
107
+ operator: "#89ddff",
108
+ };
109
+ // --- One (One Light / One Dark) ------------------------------------------
110
+ const oneLight = {
111
+ base: "#383a42",
112
+ keyword: "#a626a4",
113
+ comment: "#a0a1a7",
114
+ string: "#50a14f",
115
+ number: "#986801",
116
+ function: "#4078f2",
117
+ type: "#c18401",
118
+ tag: "#e45649",
119
+ attribute: "#986801",
120
+ operator: "#0184bc",
121
+ };
122
+ const oneDark = {
123
+ base: "#abb2bf",
124
+ keyword: "#c678dd",
125
+ comment: "#5c6370",
126
+ string: "#98c379",
127
+ number: "#d19a66",
128
+ function: "#61afef",
129
+ type: "#e5c07b",
130
+ tag: "#e06c75",
131
+ attribute: "#d19a66",
132
+ operator: "#56b6c2",
133
+ };
134
+ // --- Nord (Snow Storm light / Polar Night dark) ---------------------------
135
+ const nordLight = {
136
+ base: "#2e3440",
137
+ keyword: "#5e81ac",
138
+ comment: "#6b7280",
139
+ string: "#4f6f3a",
140
+ number: "#8f5e91",
141
+ function: "#2e6f8e",
142
+ type: "#3b7f87",
143
+ tag: "#5e81ac",
144
+ attribute: "#8f5e91",
145
+ operator: "#5e81ac",
146
+ };
147
+ const nordDark = {
148
+ base: "#d8dee9",
149
+ keyword: "#81a1c1",
150
+ comment: "#616e88",
151
+ string: "#a3be8c",
152
+ number: "#b48ead",
153
+ function: "#88c0d0",
154
+ type: "#8fbcbb",
155
+ tag: "#81a1c1",
156
+ attribute: "#8fbcbb",
157
+ operator: "#81a1c1",
158
+ };
159
+ // --- Gruvbox (Light / Dark) ----------------------------------------------
160
+ const gruvboxLight = {
161
+ base: "#3c3836",
162
+ keyword: "#9d0006",
163
+ comment: "#928374",
164
+ string: "#79740e",
165
+ number: "#8f3f71",
166
+ function: "#427b58",
167
+ type: "#b57614",
168
+ tag: "#076678",
169
+ attribute: "#b57614",
170
+ operator: "#af3a03",
171
+ };
172
+ const gruvboxDark = {
173
+ base: "#ebdbb2",
174
+ keyword: "#fb4934",
175
+ comment: "#928374",
176
+ string: "#b8bb26",
177
+ number: "#d3869b",
178
+ function: "#8ec07c",
179
+ type: "#fabd2f",
180
+ tag: "#83a598",
181
+ attribute: "#fabd2f",
182
+ operator: "#fe8019",
183
+ };
184
+ // --- Solarized (Light / Dark — shared accents, different base/comment) ----
185
+ const solarizedLight = {
186
+ base: "#657b83",
187
+ keyword: "#859900",
188
+ comment: "#93a1a1",
189
+ string: "#2aa198",
190
+ number: "#d33682",
191
+ function: "#268bd2",
192
+ type: "#b58900",
193
+ tag: "#268bd2",
194
+ attribute: "#b58900",
195
+ operator: "#859900",
196
+ };
197
+ const solarizedDark = {
198
+ base: "#839496",
199
+ keyword: "#859900",
200
+ comment: "#586e75",
201
+ string: "#2aa198",
202
+ number: "#d33682",
203
+ function: "#268bd2",
204
+ type: "#b58900",
205
+ tag: "#268bd2",
206
+ attribute: "#b58900",
207
+ operator: "#859900",
208
+ };
209
+ export function isSyntaxThemeId(value) {
210
+ return SYNTAX_THEME_IDS.includes(value);
211
+ }
212
+ // Resolve a theme id + the app's color scheme to a full token palette. Only the
213
+ // light/dark axis is coupled to the app; the theme brand is the user's choice.
214
+ export function resolveSyntaxColors(id, colorScheme) {
215
+ const dark = colorScheme === "dark";
216
+ switch (id) {
217
+ case "github":
218
+ return dark ? darkHighlightColors : lightHighlightColors;
219
+ case "catppuccin":
220
+ return expandRolePalette(dark ? catppuccinMocha : catppuccinLatte);
221
+ case "dracula":
222
+ return expandRolePalette(dracula);
223
+ case "tokyo-night":
224
+ return expandRolePalette(dark ? tokyoNight : tokyoDay);
225
+ case "one":
226
+ return expandRolePalette(dark ? oneDark : oneLight);
227
+ case "nord":
228
+ return expandRolePalette(dark ? nordDark : nordLight);
229
+ case "gruvbox":
230
+ return expandRolePalette(dark ? gruvboxDark : gruvboxLight);
231
+ case "solarized":
232
+ return expandRolePalette(dark ? solarizedDark : solarizedLight);
233
+ }
234
+ }
235
+ //# sourceMappingURL=themes.js.map
@@ -0,0 +1,6 @@
1
+ export type HighlightStyle = "keyword" | "comment" | "string" | "number" | "literal" | "function" | "definition" | "class" | "type" | "tag" | "attribute" | "property" | "variable" | "operator" | "punctuation" | "regexp" | "escape" | "meta" | "heading" | "link";
2
+ export interface HighlightToken {
3
+ text: string;
4
+ style: HighlightStyle | null;
5
+ }
6
+ //# sourceMappingURL=types.d.ts.map
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@hyperdrive.bot/paseo-highlight",
3
+ "version": "0.2.5",
4
+ "files": [
5
+ "dist",
6
+ "!dist/**/*.map"
7
+ ],
8
+ "type": "module",
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "source": "./src/index.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "scripts": {
22
+ "clean": "node ../../scripts/clean-package-dist.mjs",
23
+ "build": "tsc -p tsconfig.json --incremental false",
24
+ "build:clean": "npm run clean && npm run build",
25
+ "prepack": "npm run build:clean",
26
+ "test": "vitest run",
27
+ "typecheck": "tsgo --noEmit"
28
+ },
29
+ "dependencies": {
30
+ "@codemirror/language": "^6.12.3",
31
+ "@codemirror/legacy-modes": "^6.5.3",
32
+ "@lezer/common": "^1.5.0",
33
+ "@lezer/cpp": "^1.1.5",
34
+ "@lezer/css": "^1.3.0",
35
+ "@lezer/go": "^1.0.1",
36
+ "@lezer/highlight": "^1.2.3",
37
+ "@lezer/html": "^1.3.13",
38
+ "@lezer/java": "^1.1.3",
39
+ "@lezer/javascript": "^1.5.4",
40
+ "@lezer/json": "^1.0.3",
41
+ "@lezer/markdown": "^1.6.2",
42
+ "@lezer/php": "^1.0.5",
43
+ "@lezer/python": "^1.1.18",
44
+ "@lezer/rust": "^1.0.2",
45
+ "@lezer/xml": "^1.0.6",
46
+ "@lezer/yaml": "^1.0.4",
47
+ "@replit/codemirror-lang-csharp": "^6.2.0",
48
+ "lezer-elixir": "^1.1.2"
49
+ },
50
+ "devDependencies": {
51
+ "typescript": "^5.9.2",
52
+ "vitest": "^4.1.6"
53
+ }
54
+ }