|
3 | 3 | using System.Diagnostics; |
4 | 4 | using System.IO; |
5 | 5 | using System.Linq; |
6 | | -using System.Text; |
7 | 6 | using ExcelDna.Integration; |
8 | 7 |
|
9 | 8 | namespace ExcelDna.IntelliSense |
10 | 9 | { |
11 | | - // An IntelliSenseProvider is a part of an IntelliSenseServer that provides IntelliSense info to the server. |
12 | | - // The providers are built in to the ExcelDna.IntelliSense assembly - there are complications in making this a part that can be extended |
| 10 | + // An IntelliSenseProvider is the part of an IntelliSenseServer that provides the IntelliSense info gathered from add-ins to the server. |
| 11 | + // These providers are built in to the ExcelDna.IntelliSense assembly - there are complications in making this a part that can be extended |
13 | 12 | // by a specific add-in (server activation, cross AppDomain loading etc.). |
| 13 | + // |
14 | 14 | // Higher versions of the ExcelDna.IntelliSenseServer are expected to increase the number of providers |
15 | 15 | // and/or the scope of some provider (e.g. add support for enums). |
| 16 | + // No provision is made at the moment for user-created providers or an external provider API. |
16 | 17 |
|
17 | 18 | // The server, upon activation and at other times (when? when ExcelDna.IntelliSense.Refresh is called ?) will call the provider to get the IntelliSense info. |
18 | | - // Maybe the provider can also raise an Invalidate event, to prod the server into reloading the IntelliSense info for that provider. |
| 19 | + // Maybe the provider can also raise an Invalidate event, to prod the server into reloading the IntelliSense info for that provider |
| 20 | + // (a bit like the ribbon Invalidate works). |
19 | 21 | // E.g. the XmlProvider might read from a file, and put a FileWatcher on the file so that whenever the file changes, |
20 | 22 | // the server calls back to get the updated info. |
21 | 23 |
|
@@ -116,7 +118,7 @@ public IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos() |
116 | 118 | FunctionName = functionName, |
117 | 119 | Description = description, |
118 | 120 | ArgumentList = argumentInfos, |
119 | | - XllPath = _xllPath |
| 121 | + SourcePath = _xllPath |
120 | 122 | }; |
121 | 123 | } |
122 | 124 | } |
@@ -163,15 +165,130 @@ IEnumerable<string> GetLoadedXllPaths() |
163 | 165 | } |
164 | 166 | } |
165 | 167 |
|
166 | | - // VBA might be easy, since the functions are scoped to the Workbook or add-in, and so might have the right name? |
167 | | - // Excel4v(xlcRun, &xlRes, 1, TempStr(" myVBAAddin.xla!myFunc")); |
| 168 | + // For VBA code, (either in a regular workbook that is open, or in an add-in) |
| 169 | + // we allow the IntelliSense info to be put into a worksheet (possibly hidden or very hidden) |
| 170 | + // In the Workbook that contains the VBA. |
| 171 | + // Initially we won't scope the IntelliSense to the Workbook where the UDFs are defined, |
| 172 | + // but we should consider that. |
168 | 173 |
|
169 | | - class VbaIntelliSenseProvider |
| 174 | + class WorkbookIntelliSenseProvider : IIntelliSenseProvider |
170 | 175 | { |
| 176 | + const string intelliSenseWorksheetName = "_IntelliSense_FunctionInfo_"; |
| 177 | + class WorkbookRegistrationInfo |
| 178 | + { |
| 179 | + readonly string _name; |
| 180 | + DateTime _lastUpdate; // Version indicator to enumerate from scratch |
| 181 | + object[,] _regInfo = null; // Default value |
| 182 | + |
| 183 | + public WorkbookRegistrationInfo(string name) |
| 184 | + { |
| 185 | + _name = name; |
| 186 | + } |
| 187 | + |
| 188 | + // Called in a macro context |
| 189 | + public void Refresh() |
| 190 | + { |
| 191 | + dynamic app = ExcelDnaUtil.Application; |
| 192 | + var wb = app.Workbooks[_name]; |
| 193 | + |
| 194 | + try |
| 195 | + { |
| 196 | + var ws = wb.Sheets[intelliSenseWorksheetName]; |
| 197 | + var info = ws.UsedRange.Value; |
| 198 | + _regInfo = info as object[,]; |
| 199 | + } |
| 200 | + catch (Exception ex) |
| 201 | + { |
| 202 | + // We expect this if there is no sheet. |
| 203 | + // Another approach would be xlSheetNm |
| 204 | + Debug.Print("WorkbookIntelliSenseProvider.Refresh Error : " + ex.Message); |
| 205 | + _regInfo = null; |
| 206 | + } |
| 207 | + _lastUpdate = DateTime.Now; |
| 208 | + } |
| 209 | + |
| 210 | + // Not in macro context - don't call Excel, could be any thread. |
| 211 | + public IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos() |
| 212 | + { |
| 213 | + // to avoid worries about locking and this being updated from another thread, we take a copy of _regInfo |
| 214 | + var regInfo = _regInfo; |
| 215 | + /* |
| 216 | + result[0, 0] = XlAddIn.PathXll; |
| 217 | + result[0, 1] = registrationInfoVersion; |
| 218 | + */ |
| 219 | + |
| 220 | + if (regInfo == null) |
| 221 | + yield break; |
| 222 | + |
| 223 | + // regInfo is 1-based: object[1..x, 1..y]. |
| 224 | + for (int i = 1; i <= regInfo.GetLength(0); i++) |
| 225 | + { |
| 226 | + string functionName = regInfo[i, 1] as string; |
| 227 | + string description = regInfo[i, 2] as string; |
| 228 | + |
| 229 | + List<IntelliSenseFunctionInfo.ArgumentInfo> argumentInfos = new List<IntelliSenseFunctionInfo.ArgumentInfo>(); |
| 230 | + for (int j = 3; j <= regInfo.GetLength(1) - 1; j += 2) |
| 231 | + { |
| 232 | + var arg = regInfo[i, j] as string; |
| 233 | + var argDesc = regInfo[i, j + 1] as string; |
| 234 | + if (!string.IsNullOrEmpty(arg)) |
| 235 | + { |
| 236 | + argumentInfos.Add(new IntelliSenseFunctionInfo.ArgumentInfo |
| 237 | + { |
| 238 | + ArgumentName = arg, |
| 239 | + Description = argDesc |
| 240 | + }); |
| 241 | + } |
| 242 | + } |
| 243 | + |
| 244 | + yield return new IntelliSenseFunctionInfo |
| 245 | + { |
| 246 | + FunctionName = functionName, |
| 247 | + Description = description, |
| 248 | + ArgumentList = argumentInfos, |
| 249 | + SourcePath = _name |
| 250 | + }; |
| 251 | + } |
| 252 | + } |
| 253 | + } |
| 254 | + |
| 255 | + Dictionary<string, WorkbookRegistrationInfo> _workbookRegistrationInfos = new Dictionary<string, WorkbookRegistrationInfo>(); |
| 256 | + |
| 257 | + public void Refresh() |
| 258 | + { |
| 259 | + foreach (var name in GetLoadedWorkbookNames()) |
| 260 | + { |
| 261 | + WorkbookRegistrationInfo regInfo; |
| 262 | + if (!_workbookRegistrationInfos.TryGetValue(name, out regInfo)) |
| 263 | + { |
| 264 | + regInfo = new WorkbookRegistrationInfo(name); |
| 265 | + _workbookRegistrationInfos[name] = regInfo; |
| 266 | + } |
| 267 | + regInfo.Refresh(); |
| 268 | + } |
| 269 | + } |
| 270 | + |
| 271 | + public IEnumerable<IntelliSenseFunctionInfo> GetFunctionInfos() |
| 272 | + { |
| 273 | + return _workbookRegistrationInfos.Values.SelectMany(ri => ri.GetFunctionInfos()); |
| 274 | + } |
| 275 | + |
| 276 | + // Called in macro context |
| 277 | + // Might be implemented by tracking Application events |
| 278 | + // Remember this changes when a workbook is saved, and can refer to the wrong workbook as they are closed / opened |
| 279 | + IEnumerable<string> GetLoadedWorkbookNames() |
| 280 | + { |
| 281 | + // TODO: Implement properly... |
| 282 | + dynamic app = ExcelDnaUtil.Application; |
| 283 | + foreach (var wb in app.Workbooks) |
| 284 | + { |
| 285 | + yield return wb.Name; |
| 286 | + } |
| 287 | + } |
171 | 288 | } |
172 | 289 |
|
173 | 290 | // The idea is that other add-in tools like XLW or XLL+ could provide IntelliSense info with an xml file. |
174 | | - // CONSIDER: How to find these files - can't jsut be relative to the IntelliSense add-in. |
| 291 | + // CONSIDER: How to find these files - can't just be relative to the IntelliSense add-in. |
175 | 292 | // (Maybe next to the foreign .xll file (.xllinfo)?) |
176 | 293 | class XmlIntelliSenseProvider |
177 | 294 | { |
|
0 commit comments