summaryrefslogtreecommitdiff
path: root/src/server/document_symbols.odin
blob: 8d6d8014cece2b4ee03a36f3b68962516b47bc82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package server

import "core:odin/ast"

import "src:common"

get_document_symbols :: proc(document: ^Document) -> []DocumentSymbol {
	ast_context := make_ast_context(
		document.ast,
		document.imports,
		document.package_name,
		document.uri.uri,
		document.fullpath,
	)

	get_globals(document.ast, &ast_context)

	symbols := make([dynamic]DocumentSymbol, context.temp_allocator)

	package_symbol: DocumentSymbol

	if len(document.ast.decls) == 0 {
		return {}
	}

	for k, global in ast_context.globals {
		symbol: DocumentSymbol
		symbol.selectionRange = common.get_token_range(global.name_expr, ast_context.file.src)
		symbol.range = common.get_token_range(global.expr, ast_context.file.src)
		ensure_selection_range_contained(&symbol.range, symbol.selectionRange)
		symbol.name = k

		#partial switch v in global.expr.derived {
		case ^ast.Struct_Type, ^ast.Bit_Field_Type:
			// TODO: this only does the top level fields, we may want to travers all the way down in the future
			if s, ok := resolve_type_expression(&ast_context, global.expr); ok {
				#partial switch v in s.value {
				case SymbolStructValue:
					children := make([dynamic]DocumentSymbol, context.temp_allocator)
					for name, i in v.names {
						if name == "" {
							continue
						}
						child: DocumentSymbol
						child.range = v.ranges[i]
						child.selectionRange = v.ranges[i]
						child.name = name
						child.kind = .Field
						append(&children, child)
					}
					symbol.children = children[:]
				case SymbolBitFieldValue:
					children := make([dynamic]DocumentSymbol, context.temp_allocator)
					for name, i in v.names {
						if name == "" {
							continue
						}
						child: DocumentSymbol
						child.range = v.ranges[i]
						child.selectionRange = v.ranges[i]
						child.name = name
						child.kind = .Field
						append(&children, child)
					}
					symbol.children = children[:]
				}
			}
			symbol.kind = .Struct
		case ^ast.Proc_Lit, ^ast.Proc_Group:
			symbol.kind = .Function
		case ^ast.Enum_Type, ^ast.Union_Type:
			symbol.kind = .Enum
		case ^ast.Comp_Lit:
			if s, ok := resolve_type_expression(&ast_context, v); ok {
				ranges :: struct {
					range: common.Range,
					selection_range: common.Range,
				}
				name_map := make(map[string]ranges)
				for elem in v.elems {
					if field_value, ok := elem.derived.(^ast.Field_Value); ok {
						if name, ok := field_value.field.derived.(^ast.Ident); ok {
							selection_range := common.get_token_range(name, ast_context.file.src)
							range := common.get_token_range(field_value, ast_context.file.src)
							ensure_selection_range_contained(&range, selection_range)
							name_map[name.name] = {
								range = range,
								selection_range = selection_range,
							}
						}
					}
				}
				#partial switch v in s.value {
				case SymbolStructValue:
					children := make([dynamic]DocumentSymbol, context.temp_allocator)
					for name, i in v.names {
						child: DocumentSymbol
						if range, ok := name_map[name]; ok {
							child.range = range.range
							child.selectionRange = range.selection_range
							child.name = name
							child.kind = .Field
							append(&children, child)
						}
					}
					symbol.children = children[:]
				case SymbolBitFieldValue:
					children := make([dynamic]DocumentSymbol, context.temp_allocator)
					for name, i in v.names {
						child: DocumentSymbol
						if range, ok := name_map[name]; ok {
							child.range = range.range
							child.selectionRange = range.selection_range
							child.name = name
							child.kind = .Field
							append(&children, child)
						}
					}
					symbol.children = children[:]
				}
			}
		case:
			symbol.kind = .Variable
		}

		append(&symbols, symbol)
	}


	return symbols[:]
}

@(private="file")
ensure_selection_range_contained :: proc(range: ^common.Range, selection_range: common.Range) {
	// selection range must be contained with range, so we set the range start to be the selection range start
	range.start = selection_range.start

	// if the range end is somehow before the selection_range end, we set it to the end of the selection range
	if range.end.line < selection_range.end.line {
		range.end = selection_range.end
	} else if range.end.line == selection_range.end.line && range.end.character < selection_range.end.character {
		range.end = selection_range.end
	}
}