@@ -74,6 +74,7 @@ pub const Language = enum(u8) {
7474 rust ,
7575 go_lang ,
7676 php ,
77+ csharp ,
7778 markdown ,
7879 json ,
7980 yaml ,
@@ -90,6 +91,7 @@ pub fn detectLanguage(path: []const u8) Language {
9091 if (std .mem .endsWith (u8 , path , ".rs" )) return .rust ;
9192 if (std .mem .endsWith (u8 , path , ".go" )) return .go_lang ;
9293 if (std .mem .endsWith (u8 , path , ".php" )) return .php ;
94+ if (std .mem .endsWith (u8 , path , ".cs" )) return .csharp ;
9395 if (std .mem .endsWith (u8 , path , ".md" )) return .markdown ;
9496 if (std .mem .endsWith (u8 , path , ".json" )) return .json ;
9597 if (std .mem .endsWith (u8 , path , ".yaml" ) or std .mem .endsWith (u8 , path , ".yml" )) return .yaml ;
@@ -195,6 +197,8 @@ fn indexFileInner(self: *Explorer, path: []const u8, content: []const u8, full_i
195197 try self .parseRustLine (trimmed , line_num , & outline , prev_line_trimmed );
196198 } else if (outline .language == .php ) {
197199 try self .parsePhpLine (trimmed , line_num , & outline , & php_state );
200+ } else if (outline .language == .csharp ) {
201+ try self .parseCSharpLine (trimmed , line_num , & outline );
198202 }
199203
200204 prev_line_trimmed = trimmed ;
@@ -1200,6 +1204,228 @@ pub fn getHotFiles(self: *Explorer, store: *Store, allocator: std.mem.Allocator,
12001204 return null ;
12011205 }
12021206
1207+ fn parseCSharpLine (self : * Explorer , line : []const u8 , line_num : u32 , outline : * FileOutline ) ! void {
1208+ const a = self .allocator ;
1209+
1210+ if (startsWith (line , "using " )) {
1211+ const symbol_copy = try a .dupe (u8 , line );
1212+ errdefer a .free (symbol_copy );
1213+ try outline .symbols .append (a , .{
1214+ .name = symbol_copy ,
1215+ .kind = .import ,
1216+ .line_start = line_num ,
1217+ .line_end = line_num ,
1218+ });
1219+ const import_copy = try a .dupe (u8 , line );
1220+ errdefer a .free (import_copy );
1221+ try outline .imports .append (a , import_copy );
1222+ return ;
1223+ }
1224+
1225+ if (startsWith (line , "namespace " )) {
1226+ if (extractIdent (line [10.. ])) | name | {
1227+ const name_copy = try a .dupe (u8 , name );
1228+ errdefer a .free (name_copy );
1229+ const detail_copy = try a .dupe (u8 , line );
1230+ errdefer a .free (detail_copy );
1231+ try outline .symbols .append (a , .{
1232+ .name = name_copy ,
1233+ .kind = .import ,
1234+ .line_start = line_num ,
1235+ .line_end = line_num ,
1236+ .detail = detail_copy ,
1237+ });
1238+ }
1239+ return ;
1240+ }
1241+
1242+ const stripped = stripCSharpModifiers (line );
1243+
1244+ if (startsWith (stripped , "class " )) {
1245+ if (extractIdent (stripped [6.. ])) | name | {
1246+ const name_copy = try a .dupe (u8 , name );
1247+ errdefer a .free (name_copy );
1248+ const detail_copy = try a .dupe (u8 , line );
1249+ errdefer a .free (detail_copy );
1250+ try outline .symbols .append (a , .{
1251+ .name = name_copy ,
1252+ .kind = .struct_def ,
1253+ .line_start = line_num ,
1254+ .line_end = line_num ,
1255+ .detail = detail_copy ,
1256+ });
1257+ }
1258+ return ;
1259+ }
1260+
1261+ if (startsWith (stripped , "interface " )) {
1262+ if (extractIdent (stripped [10.. ])) | name | {
1263+ const name_copy = try a .dupe (u8 , name );
1264+ errdefer a .free (name_copy );
1265+ const detail_copy = try a .dupe (u8 , line );
1266+ errdefer a .free (detail_copy );
1267+ try outline .symbols .append (a , .{
1268+ .name = name_copy ,
1269+ .kind = .trait_def ,
1270+ .line_start = line_num ,
1271+ .line_end = line_num ,
1272+ .detail = detail_copy ,
1273+ });
1274+ }
1275+ return ;
1276+ }
1277+
1278+ if (startsWith (stripped , "struct " )) {
1279+ if (extractIdent (stripped [7.. ])) | name | {
1280+ const name_copy = try a .dupe (u8 , name );
1281+ errdefer a .free (name_copy );
1282+ const detail_copy = try a .dupe (u8 , line );
1283+ errdefer a .free (detail_copy );
1284+ try outline .symbols .append (a , .{
1285+ .name = name_copy ,
1286+ .kind = .struct_def ,
1287+ .line_start = line_num ,
1288+ .line_end = line_num ,
1289+ .detail = detail_copy ,
1290+ });
1291+ }
1292+ return ;
1293+ }
1294+
1295+ if (startsWith (stripped , "enum " )) {
1296+ if (extractIdent (stripped [5.. ])) | name | {
1297+ const name_copy = try a .dupe (u8 , name );
1298+ errdefer a .free (name_copy );
1299+ const detail_copy = try a .dupe (u8 , line );
1300+ errdefer a .free (detail_copy );
1301+ try outline .symbols .append (a , .{
1302+ .name = name_copy ,
1303+ .kind = .enum_def ,
1304+ .line_start = line_num ,
1305+ .line_end = line_num ,
1306+ .detail = detail_copy ,
1307+ });
1308+ }
1309+ return ;
1310+ }
1311+
1312+ if (startsWith (stripped , "delegate " )) {
1313+ if (std .mem .indexOf (u8 , stripped , "(" )) | paren | {
1314+ const before_paren = std .mem .trimRight (u8 , stripped [9.. paren ], " \t " );
1315+ if (lastIdent (before_paren )) | name | {
1316+ const name_copy = try a .dupe (u8 , name );
1317+ errdefer a .free (name_copy );
1318+ const detail_copy = try a .dupe (u8 , line );
1319+ errdefer a .free (detail_copy );
1320+ try outline .symbols .append (a , .{
1321+ .name = name_copy ,
1322+ .kind = .type_alias ,
1323+ .line_start = line_num ,
1324+ .line_end = line_num ,
1325+ .detail = detail_copy ,
1326+ });
1327+ }
1328+ }
1329+ return ;
1330+ }
1331+
1332+ if (startsWith (stripped , "const " )) {
1333+ const after = stripped [6.. ];
1334+ if (std .mem .indexOf (u8 , after , "=" )) | eq | {
1335+ const before_eq = std .mem .trimRight (u8 , after [0.. eq ], " \t " );
1336+ if (lastIdent (before_eq )) | name | {
1337+ const name_copy = try a .dupe (u8 , name );
1338+ errdefer a .free (name_copy );
1339+ const detail_copy = try a .dupe (u8 , line );
1340+ errdefer a .free (detail_copy );
1341+ try outline .symbols .append (a , .{
1342+ .name = name_copy ,
1343+ .kind = .constant ,
1344+ .line_start = line_num ,
1345+ .line_end = line_num ,
1346+ .detail = detail_copy ,
1347+ });
1348+ }
1349+ }
1350+ return ;
1351+ }
1352+
1353+ if (std .mem .indexOf (u8 , stripped , "(" )) | paren | {
1354+ if (paren > 0 ) {
1355+ const control = [_ ][]const u8 { "if " , "if(" , "else " , "for " , "for(" , "foreach " , "foreach(" , "while " , "while(" , "switch " , "switch(" , "catch " , "catch(" , "return " , "return(" , "new " , "throw " , "lock " , "lock(" };
1356+ for (control ) | kw | {
1357+ if (startsWith (stripped , kw )) return ;
1358+ }
1359+ const before_paren = std .mem .trimRight (u8 , stripped [0.. paren ], " \t " );
1360+ if (lastIdent (before_paren )) | name | {
1361+ if (std .mem .eql (u8 , name , "base" ) or std .mem .eql (u8 , name , "this" ) or std .mem .eql (u8 , name , "value" )) return ;
1362+ const name_copy = try a .dupe (u8 , name );
1363+ errdefer a .free (name_copy );
1364+ const detail_copy = try a .dupe (u8 , line );
1365+ errdefer a .free (detail_copy );
1366+ try outline .symbols .append (a , .{
1367+ .name = name_copy ,
1368+ .kind = .method ,
1369+ .line_start = line_num ,
1370+ .line_end = line_num ,
1371+ .detail = detail_copy ,
1372+ });
1373+ }
1374+ }
1375+ }
1376+ }
1377+
1378+ fn stripCSharpModifiers (line : []const u8 ) []const u8 {
1379+ const modifiers = [_ ][]const u8 {
1380+ "public " , "private " , "protected " , "internal " ,
1381+ "static " , "abstract " , "virtual " , "override " ,
1382+ "sealed " , "async " , "partial " , "readonly " ,
1383+ "new " , "extern " , "unsafe " , "volatile " ,
1384+ };
1385+ var result = line ;
1386+ var changed = true ;
1387+ while (changed ) {
1388+ changed = false ;
1389+ for (modifiers ) | mod | {
1390+ if (std .mem .startsWith (u8 , result , mod )) {
1391+ result = result [mod .len .. ];
1392+ changed = true ;
1393+ }
1394+ }
1395+ }
1396+ return result ;
1397+ }
1398+
1399+ fn lastIdent (s : []const u8 ) ? []const u8 {
1400+ if (s .len == 0 ) return null ;
1401+ var end = s .len ;
1402+ if (s [end - 1 ] == '>' ) {
1403+ var depth : u32 = 0 ;
1404+ var i = end ;
1405+ while (i > 0 ) : (i -= 1 ) {
1406+ if (s [i - 1 ] == '>' ) depth += 1 ;
1407+ if (s [i - 1 ] == '<' ) {
1408+ depth -= 1 ;
1409+ if (depth == 0 ) {
1410+ end = i - 1 ;
1411+ break ;
1412+ }
1413+ }
1414+ }
1415+ }
1416+ while (end > 0 and s [end - 1 ] == ']' ) {
1417+ if (end >= 2 and s [end - 2 ] == '[' ) {
1418+ end -= 2 ;
1419+ } else break ;
1420+ }
1421+ var ident_end = end ;
1422+ while (ident_end > 0 and (std .ascii .isAlphanumeric (s [ident_end - 1 ]) or s [ident_end - 1 ] == '_' )) {
1423+ ident_end -= 1 ;
1424+ }
1425+ if (ident_end == end ) return null ;
1426+ return s [ident_end .. end ];
1427+ }
1428+
12031429fn rebuildDepsFor (self : * Explorer , path : []const u8 , outline : * FileOutline ) ! void {
12041430 var deps : std .ArrayList ([]const u8 ) = .{};
12051431 errdefer deps .deinit (self .allocator );
@@ -1418,7 +1644,7 @@ pub fn isCommentOrBlank(line: []const u8, language: Language) bool {
14181644 const trimmed = std .mem .trim (u8 , line , " \t " );
14191645 if (trimmed .len == 0 ) return true ;
14201646 return switch (language ) {
1421- .zig , .rust , .go_lang = > std .mem .startsWith (u8 , trimmed , "//" ),
1647+ .zig , .rust , .go_lang , .csharp = > std .mem .startsWith (u8 , trimmed , "//" ),
14221648 .python = > std .mem .startsWith (u8 , trimmed , "#" ),
14231649 .javascript , .typescript , .c , .cpp = > std .mem .startsWith (u8 , trimmed , "//" ) or std .mem .startsWith (u8 , trimmed , "/*" ) or std .mem .startsWith (u8 , trimmed , "*" ),
14241650 else = > false ,
0 commit comments