理論學習需工具驗證。本文指導開發一個 Eclipse View 插件,實時可視化當前編輯器的 `IStructuredDocument` 節點樹與文本區域劃分,支援雙向同步選中(編輯器光標 ↔ 視圖節點),作為理解 SSE 語法模型的「瑞士軍刀」,並為後續 Model 視圖開發建立範式。
| 交互方向 | 觸發條件 | 響應行為 |
|---|---|---|
| 編輯器 → 視圖 | 編輯器激活 | 加載並顯示該編輯器的 Document 節點樹 |
| 光標位置變化 | 視圖自動展開並選中對應節點 | |
| 文本內容修改 | 視圖增量更新樹結構(保持展開狀態) | |
| 編輯器關閉 | 清空視圖內容 | |
| 視圖 → 編輯器 | 雙擊樹節點 | 編輯器高亮選中對應文本區域 |
WTP 利用 `IAdaptable` / `IAdapterFactory` 為編輯器提供屬性視圖支援。`StructuredTextEditor.getAdapter()` 中註冊 `ConfigurablePropertySheetPage`,根據光標位置顯示對應標籤屬性編輯界面。理解此機制有助於自定義視圖集成。
public class StructuredDocumentView extends ViewPart {
private TreeViewer treeViewer;
private IEditorPart activeEditor;
private IStructuredDocument currentDocument;
private IPartListener partListener = new IPartListener() {
public void partActivated(IWorkbenchPart part) {
if (part instanceof StructuredTextEditor) {
hookEditor((StructuredTextEditor) part);
}
}
public void partClosed(IWorkbenchPart part) {
if (part == activeEditor) clearViewer();
}
// ... partBroughtToTop, partDeactivated, partOpened
};
private void hookEditor(StructuredTextEditor editor) {
activeEditor = editor;
IDocumentProvider provider = editor.getDocumentProvider();
IDocument doc = provider.getDocument(editor.getEditorInput());
if (doc instanceof IStructuredDocument) {
currentDocument = (IStructuredDocument) doc;
// 1. 構建樹輸入
treeViewer.setInput(currentDocument.getStructuredNode());
// 2. 監聽文本變更 → 增量刷新
currentDocument.addDocumentListener(new DocumentListener() {
public void documentChanged(DocumentEvent e) {
// 僅刷新受影響子樹,避免全量重建
treeViewer.refresh(); // 簡化處理,生產環境需精細化
}
});
// 3. 監聽選中變化 → 同步光標
editor.getSelectionProvider().addSelectionChangedListener(event -> {
ISelection sel = event.getSelection();
if (sel instanceof ITextSelection) {
int offset = ((ITextSelection) sel).getOffset();
IStructuredNode node = currentDocument.findNodeAtOffset(offset);
if (node != null) treeViewer.setSelection(new StructuredSelection(node), true);
}
});
}
}
}
class NodeContentProvider implements ITreeContentProvider {
public Object[] getChildren(Object parent) {
if (parent instanceof IStructuredNode) {
return ((IStructuredNode) parent).getChildren().toArray();
}
return new Object[0];
}
public Object getParent(Object child) {
return child instanceof IStructuredNode ? ((IStructuredNode) child).getParent() : null;
}
public boolean hasChildren(Object obj) { return getChildren(obj).length > 0; }
public Object[] getElements(Object input) { return getChildren(input); }
}
class NodeLabelProvider extends LabelProvider {
public String getText(Object obj) {
if (obj instanceof IStructuredNode) {
IStructuredNode node = (IStructuredNode) obj;
return String.format("%s [%d-%d]", node.getType(), node.getStartOffset(), node.getEndOffset());
}
return super.getText(obj);
}
}
treeViewer.addDoubleClickListener(event -> {
IStructuredSelection sel = (IStructuredSelection) event.getSelection();
IStructuredNode node = (IStructuredNode) sel.getFirstElement();
if (node != null && activeEditor instanceof StructuredTextEditor) {
IDocument doc = ((StructuredTextEditor) activeEditor).getDocumentProvider()
.getDocument(activeEditor.getEditorInput());
// 高亮選中區域
((StructuredTextEditor) activeEditor).selectAndReveal(node.getStartOffset(), node.getEndOffset() - node.getStartOffset());
}
});
WTP 在 `StructuredTextEditor.getAdapter(Class)` 中:
public Object getAdapter(Class required) {
if (IPropertySheetPage.class.equals(required) && isEditable()) {
if (fPropertySheetPage == null || fPropertySheetPage.getControl().isDisposed()) {
PropertySheetConfiguration cfg = createPropertySheetConfiguration();
if (cfg != null) {
ConfigurablePropertySheetPage page = new ConfigurablePropertySheetPage();
page.setConfiguration(cfg);
fPropertySheetPage = page;
}
}
return fPropertySheetPage;
}
return super.getAdapter(required);
}
`PropertySheetConfiguration` 定義了當前光標節點支援的屬性頁面(如 JSP 指令屬性、HTML 標籤屬性),實現了**上下文敏感的屬性編輯**。
使用本視圖對任意 JSP 文件進行觀察,驗證以下認知:
開發 `IStructuredModel (DOM Document)` 分析視圖:可視化語義模型(`IDOMDocument`、`ICSSDocument`),對比語法樹與語義樹差異,理解為什麼高級功能需要語義模型支撐。