Skip to content

Commit

Permalink
Adding sections information to the Outline (mathworks#24)
Browse files Browse the repository at this point in the history
Support for Sections in outline
  • Loading branch information
Vishnu Vardhan Sandhireddy authored and GitHub Enterprise committed Oct 4, 2023
1 parent 37e76cf commit a0fe6e5
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 6 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ MATLAB language server supports these editors by installing the corresponding ex

### Unreleased

### 1.1.6

* Add support for MATLAB sections in the documentSymbol (outline).

### 1.1.3
Release date: 2023-07-10

Fixed:
* Diagnostic suppression should be placed at correct location when '%' is contained within string
* Improved navigation to files inside MATLAB packages within the VS Code workspace but not on the MATLAB path
Expand Down
84 changes: 81 additions & 3 deletions matlab/+matlabls/+internal/computeCodeData.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@
% codeInfo.functionInfo(k).variableInfo
% codeInfo.functionInfo(k).globals
% codeInfo.functionInfo(k).isPrototype

% Copyright 2022 - 2023 The MathWorks, Inc.
%
% and codeInfo.sections is an array of struct:
% codeInfo.sections(k).title
% codeInfo.sections(k).range

% Copyright 2022 - 2023 The MathWorks, Inc.

timeStart = tic;

Expand All @@ -56,7 +60,7 @@
if ismissing(codeInfo.packageName)
codeInfo.packageName = '';
end

%% Parse code
mt = mtree(code);

Expand Down Expand Up @@ -200,15 +204,89 @@
functionReferences{startIndex + k} = { functionName, ranges(k) };
end

%% Getting Section Data
sections = {};

try
parsedCode = matlab.codeanalyzer.internal.analyzeCode(code);
isExplicitColumnPresent = ismember('isExplicit', parsedCode.CodeSections.Properties.VariableNames);
lengthOfSections = height(parsedCode.CodeSections);

sectionIndex = 1;
if lengthOfSections > 0
% Splitting the code to lines upfront, used later to detect
% explicit sections and to find the character end of line
lines = strsplit(code, newline, CollapseDelimiters = false);
end

for row = 1:lengthOfSections
title = parsedCode.CodeSections.titles(row);
startLine = parsedCode.CodeSections.startLines(row);
endLine = parsedCode.CodeSections.endLines(row);
% IsExplicit is supported from R2024a MATLAB, this information is useful to detect the empty title sections
% added by user and providing custom name.
if isExplicitColumnPresent
isExplicit = parsedCode.CodeSections.isExplicit(row);

% Filter out the sections that are not explicitly added by user
if isExplicit
if strcmp(title, "")
% Add a custom name if the section title is empty
[title, sectionIndex] = generateSectionTitleForEmpty(sectionIndex);
end
elements = createRangeForSection(title, startLine, endLine, lines);
sections = [sections, elements];
end
else
% Generate section titles for empty titles if they are explicit
if strcmp(title, "")
if ~isLineAnExplicitSection(lines, startLine)
% Section is implicit continue
continue;
end
[title, sectionIndex] = generateSectionTitleForEmpty(sectionIndex);
end
elements = createRangeForSection(title, startLine, endLine, lines);
sections = [sections, elements];
end
end

catch exception
disp(['Error occurred while fetching sections: ', exception.message]);
end

%% Finalize Data
codeInfo.functionInfo = functionMap.values;
codeInfo.references = functionReferences;
codeInfo.sections = sections;

%% Finalize timing
codeInfo.timeToIndex = toc(timeStart);
end

function [title, sectionIndex] = generateSectionTitleForEmpty(sectionIndex)
title = "Section " + sectionIndex;
sectionIndex = sectionIndex + 1;
end

function isSection = isLineAnExplicitSection(lines, lineNumber)
isSection = false;
codeLine = string(lines(lineNumber));
pattern = "^\s*%%\s*$";
% Lines that are only having "%%" are explicit sections
matches = regexp(codeLine, pattern, 'once');
if ~isempty(matches)
isSection = true;
end
end

function result = createRangeForSection (title, lineStart, lineEnd, lines)
% Get the character end of the line for the section from lines
% We have to add 1 to string length to get the column number
charEnd = strlength(string(lines(lineEnd))) + 1;
range = createRange (lineStart, 1, lineEnd, charEnd);
result = struct('title', title, 'range', range);
end
% --------- Other Parsers ---------- %
function functionReferences = parseFunctionData (functionNode, isPublic, isClassDef, className, functionMap, functionReferences)
functionName = functionNode.Fname.string();
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "matlab-language-server",
"version": "1.1.2",
"version": "1.1.6",
"description": "Language Server for MATLAB code",
"main": "./src/index.ts",
"scripts": {
Expand Down
30 changes: 28 additions & 2 deletions src/indexing/FileInfoIndex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface RawCodeData {
functionInfo: CodeDataFunctionInfo[]
packageName: string
references: CodeDataReference[]
sections: CodeDataSectionInfo[]
}

/**
Expand All @@ -28,6 +29,14 @@ interface CodeDataClassInfo {
baseClasses: string[]
}

/**
* Section data of MATLAB. Sections are groupings formed in MATLAB using %% comments.
*/
interface CodeDataSectionInfo {
title: string,
range: CodeDataRange
}

/**
* Contains raw information about a function
*/
Expand Down Expand Up @@ -352,17 +361,18 @@ class MatlabVariableInfo {
export class MatlabCodeData {
readonly functions: Map<string, MatlabFunctionInfo>
readonly references: Map<string, Range[]>

readonly sections: Map<string, Range[]>
readonly packageName: string

constructor (public uri: string, rawCodeData: RawCodeData, public classInfo?: MatlabClassInfo) {
this.functions = new Map<string, MatlabFunctionInfo>()
this.references = new Map<string, Range[]>()

this.sections = new Map<string, Range[]>()
this.packageName = rawCodeData.packageName

this.parseFunctions(rawCodeData.functionInfo)
this.parseReferences(rawCodeData.references)
this.parseSectionInfo(rawCodeData.sections)
}

/**
Expand Down Expand Up @@ -445,6 +455,22 @@ export class MatlabCodeData {
}
})
}

/**
* Parse raw section info to the section and set to this.sections
* @param sectionsInfo Array of the section information of the file retrieved from MATLAB
*/
private parseSectionInfo (sectionsInfo: CodeDataSectionInfo[]): void {
sectionsInfo.forEach((sectionInfo) => {
const { title, range: rangeSectionInfo } = sectionInfo
const range = convertRange(rangeSectionInfo)
if (!this.sections.has(title)) {
this.sections.set(title, [range])
} else {
this.sections.get(title)?.push(range)
}
})
}
}

/**
Expand Down
6 changes: 6 additions & 0 deletions src/providers/navigation/NavigationSupportProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ class NavigationSupportProvider {
classInfo.properties.forEach((info, name) => pushSymbol(name, SymbolKind.Property, info.range))
}
codeData.functions.forEach((info, name) => pushSymbol(name, info.isClassMethod ? SymbolKind.Method : SymbolKind.Function, info.range))
codeData.sections.forEach((range, title) => {
range.forEach(range => {
pushSymbol(title, SymbolKind.Module, range)
})
})

/**
* Handle a case when the indexer fails due to the user being in the middle of an edit.
* Here the documentSymbol cache has some symbols but the codeData cache has none. So we
Expand Down

0 comments on commit a0fe6e5

Please sign in to comment.